diff --git a/doc/index.rst b/doc/index.rst index a3439895..77e2feab 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -133,7 +133,7 @@ URDF: .. code-block:: xml - + $(find gz_ros2_control_demos)/config/cart_controller.yaml @@ -142,7 +142,7 @@ SDF: .. code-block:: xml - + $(find gz_ros2_control_demos)/config/cart_controller.yaml @@ -172,7 +172,7 @@ URDF: .. code-block:: xml - + ... my_namespace @@ -185,7 +185,7 @@ SDF: .. code-block:: xml - + ... my_namespace @@ -226,7 +226,7 @@ URDF: ... - + ... @@ -241,7 +241,7 @@ SDF: ... - + ... @@ -356,6 +356,10 @@ You can also manually publish on the ``/cmd_vel`` topic to drive the robots: y: 0.0 z: 1.0" +For these demos you can verify that the robot is moving at the desired velocities be echoing the ``/gz/odom`` topic. +This topic gets the simulation-real odometry of the robot from Gazebo. +You can also echo the ``/odom`` topic to verify how accurate the odometry calculated by the controller is in comparison to the odometry from ``/gz/odom``. + Gripper ----------------------------------------------------------- diff --git a/gz_ros2_control_demos/config/diff_drive_controller.yaml b/gz_ros2_control_demos/config/diff_drive_controller.yaml index 02446dff..ebb160bb 100644 --- a/gz_ros2_control_demos/config/diff_drive_controller.yaml +++ b/gz_ros2_control_demos/config/diff_drive_controller.yaml @@ -5,14 +5,13 @@ joint_state_broadcaster: type: joint_state_broadcaster/JointStateBroadcaster -/**/diff_drive_base_controller: +/**/diff_drive_controller: ros__parameters: type: diff_drive_controller/DiffDriveController left_wheel_names: ["left_wheel_joint"] right_wheel_names: ["right_wheel_joint"] wheel_separation: 1.25 - #wheels_per_side: 1 # actually 2, but both are controlled by 1 signal wheel_radius: 0.3 wheel_separation_multiplier: 1.0 @@ -29,8 +28,8 @@ enable_odom_tf: true cmd_vel_timeout: 0.5 - #publish_limited_velocity: true - #velocity_rolling_window_size: 10 + publish_limited_velocity: true + velocity_rolling_window_size: 10 # Velocity and acceleration limits # Whenever a min_* is unspecified, default to -max_* diff --git a/gz_ros2_control_demos/config/ros_gz_bridge_config.yaml b/gz_ros2_control_demos/config/ros_gz_bridge_config.yaml new file mode 100644 index 00000000..30766d5f --- /dev/null +++ b/gz_ros2_control_demos/config/ros_gz_bridge_config.yaml @@ -0,0 +1,13 @@ +--- +- ros_topic_name: "/clock" + gz_topic_name: "/clock" + ros_type_name: "rosgraph_msgs/msg/Clock" + gz_type_name: "gz.msgs.Clock" + direction: GZ_TO_ROS + +# Topic published by OdometryPublisher plugin, only for debugging purposes +- ros_topic_name: "/gz/odom" + gz_topic_name: "/gz/odom" + ros_type_name: "nav_msgs/msg/Odometry" + gz_type_name: "gz.msgs.Odometry" + direction: GZ_TO_ROS diff --git a/gz_ros2_control_demos/launch/ackermann_drive_example.launch.py b/gz_ros2_control_demos/launch/ackermann_drive_example.launch.py index 8e14c17c..76c9f301 100644 --- a/gz_ros2_control_demos/launch/ackermann_drive_example.launch.py +++ b/gz_ros2_control_demos/launch/ackermann_drive_example.launch.py @@ -12,59 +12,48 @@ # See the License for the specific language governing permissions and # limitations under the License. +from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, OpaqueFunction -from launch.actions import RegisterEventHandler -from launch.event_handlers import OnProcessExit -from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.actions import DeclareLaunchArgument, ExecuteProcess, OpaqueFunction from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution - from launch_ros.actions import Node -from launch_ros.substitutions import FindPackageShare +from ros_gz_bridge.actions import RosGzBridge +from ros_gz_sim.actions import GzServer def generate_launch_description(): + pkg_share = get_package_share_directory('gz_ros2_control_demos') + # Launch Arguments use_sim_time = LaunchConfiguration('use_sim_time', default=True) def robot_state_publisher(context): - performed_description_format = LaunchConfiguration('description_format').perform(context) + description_format = LaunchConfiguration('description_format').perform(context) # Get URDF or SDF via xacro - robot_description_content = Command( + xacro_processed = Command( [ PathJoinSubstitution([FindExecutable(name='xacro')]), ' ', PathJoinSubstitution([ - FindPackageShare('gz_ros2_control_demos'), - performed_description_format, - f'test_ackermann_drive.xacro.{performed_description_format}' + pkg_share, + description_format, + f'test_ackermann_drive.xacro.{description_format}' ]), ] ) - robot_description = {'robot_description': robot_description_content} node_robot_state_publisher = Node( package='robot_state_publisher', executable='robot_state_publisher', output='screen', - parameters=[robot_description] + parameters=[{'robot_description': xacro_processed}] ) return [node_robot_state_publisher] - robot_controllers = PathJoinSubstitution( - [ - FindPackageShare('gz_ros2_control_demos'), - 'config', - 'ackermann_drive_controller.yaml', - ] - ) - - gz_spawn_entity = Node( - package='ros_gz_sim', - executable='create', - output='screen', - arguments=['-topic', 'robot_description', '-name', - 'ackermann', '-allow_renaming', 'true'], - ) + robot_controllers = PathJoinSubstitution([ + pkg_share, + 'config', + 'ackermann_drive_controller.yaml' + ]) joint_state_broadcaster_spawner = Node( package='controller_manager', @@ -74,45 +63,32 @@ def robot_state_publisher(context): ackermann_steering_controller_spawner = Node( package='controller_manager', executable='spawner', - arguments=['ackermann_steering_controller', - '--param-file', - robot_controllers, - '--controller-ros-args', - '-r /ackermann_steering_controller/tf_odometry:=/tf', - '-r /ackermann_steering_controller/reference:=/cmd_vel' - ], + arguments=[ + 'ackermann_steering_controller', + '--param-file', robot_controllers, + '--controller-ros-args', + '-r /ackermann_steering_controller/tf_odometry:=/tf', + '-r /ackermann_steering_controller/reference:=/cmd_vel' + ], ) - # Bridge - bridge = Node( - package='ros_gz_bridge', - executable='parameter_bridge', - arguments=['/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock'], - output='screen' + # Setup ros_gz_bridge to bridge topics between ROS and Gazebo. + # It is launched as a composable node in the container created by the Gazebo server. + ros_gz_bridge = RosGzBridge( + bridge_name='ros_gz_bridge', + config_file=PathJoinSubstitution([pkg_share, 'config', 'ros_gz_bridge_config.yaml']), + container_name='ros_gz_container', + create_own_container='False', + use_composition='True', ) ld = LaunchDescription([ - bridge, - # Launch gazebo environment - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [PathJoinSubstitution([FindPackageShare('ros_gz_sim'), - 'launch', - 'gz_sim.launch.py'])]), - launch_arguments=[('gz_args', [' -r -v 1 empty.sdf'])]), - RegisterEventHandler( - event_handler=OnProcessExit( - target_action=gz_spawn_entity, - on_exit=[joint_state_broadcaster_spawner], - ) - ), - RegisterEventHandler( - event_handler=OnProcessExit( - target_action=joint_state_broadcaster_spawner, - on_exit=[ackermann_steering_controller_spawner], - ) - ), + gz_server, + gz_gui, gz_spawn_entity, + ros_gz_bridge, + joint_state_broadcaster_spawner, + ackermann_steering_controller_spawner, # Launch Arguments DeclareLaunchArgument( 'use_sim_time', diff --git a/gz_ros2_control_demos/launch/diff_drive_example.launch.py b/gz_ros2_control_demos/launch/diff_drive_example.launch.py index a0b21269..e8009e5c 100644 --- a/gz_ros2_control_demos/launch/diff_drive_example.launch.py +++ b/gz_ros2_control_demos/launch/diff_drive_example.launch.py @@ -12,66 +12,55 @@ # See the License for the specific language governing permissions and # limitations under the License. +from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, OpaqueFunction -from launch.actions import RegisterEventHandler -from launch.event_handlers import OnProcessExit -from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.actions import DeclareLaunchArgument, ExecuteProcess, OpaqueFunction from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution - from launch_ros.actions import Node -from launch_ros.substitutions import FindPackageShare +from ros_gz_bridge.actions import RosGzBridge +from ros_gz_sim.actions import GzServer def generate_launch_description(): + pkg_share = get_package_share_directory('gz_ros2_control_demos') + # Launch Arguments use_sim_time = LaunchConfiguration('use_sim_time', default=True) def robot_state_publisher(context): - performed_description_format = LaunchConfiguration('description_format').perform(context) + description_format = LaunchConfiguration('description_format').perform(context) # Get URDF or SDF via xacro - robot_description_content = Command( + xacro_processed = Command( [ PathJoinSubstitution([FindExecutable(name='xacro')]), ' ', PathJoinSubstitution([ - FindPackageShare('gz_ros2_control_demos'), - performed_description_format, - f'test_diff_drive.xacro.{performed_description_format}' + pkg_share, + description_format, + f'test_diff_drive.xacro.{description_format}' ]), ] ) - robot_description = {'robot_description': robot_description_content} node_robot_state_publisher = Node( package='robot_state_publisher', executable='robot_state_publisher', output='screen', - parameters=[robot_description] + parameters=[{'robot_description': xacro_processed}] ) return [node_robot_state_publisher] - robot_controllers = PathJoinSubstitution( - [ - FindPackageShare('gz_ros2_control_demos'), - 'config', - 'diff_drive_controller.yaml', - ] - ) - - gz_spawn_entity = Node( - package='ros_gz_sim', - executable='create', - output='screen', - arguments=['-topic', 'robot_description', '-name', - 'diff_drive', '-allow_renaming', 'true'], - ) + robot_controllers = PathJoinSubstitution([ + pkg_share, + 'config', + 'diff_drive_controller.yaml' + ]) joint_state_broadcaster_spawner = Node( package='controller_manager', executable='spawner', arguments=['joint_state_broadcaster'], ) - diff_drive_base_controller_spawner = Node( + diff_drive_controller_spawner = Node( package='controller_manager', executable='spawner', arguments=[ @@ -83,36 +72,23 @@ def robot_state_publisher(context): ], ) - # Bridge - bridge = Node( - package='ros_gz_bridge', - executable='parameter_bridge', - arguments=['/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock'], - output='screen' + # Setup ros_gz_bridge to bridge topics between ROS and Gazebo. + # It is launched as a composable node in the container created by the Gazebo server. + ros_gz_bridge = RosGzBridge( + bridge_name='ros_gz_bridge', + config_file=PathJoinSubstitution([pkg_share, 'config', 'ros_gz_bridge_config.yaml']), + container_name='ros_gz_container', + create_own_container='False', + use_composition='True', ) ld = LaunchDescription([ - # Launch gazebo environment - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [PathJoinSubstitution([FindPackageShare('ros_gz_sim'), - 'launch', - 'gz_sim.launch.py'])]), - launch_arguments=[('gz_args', [' -r -v 1 empty.sdf'])]), - RegisterEventHandler( - event_handler=OnProcessExit( - target_action=gz_spawn_entity, - on_exit=[joint_state_broadcaster_spawner], - ) - ), - RegisterEventHandler( - event_handler=OnProcessExit( - target_action=joint_state_broadcaster_spawner, - on_exit=[diff_drive_base_controller_spawner], - ) - ), - bridge, + gz_server, + gz_gui, gz_spawn_entity, + ros_gz_bridge, + joint_state_broadcaster_spawner, + diff_drive_controller_spawner, # Launch Arguments DeclareLaunchArgument( 'use_sim_time', @@ -120,7 +96,7 @@ def robot_state_publisher(context): description='If true, use simulated clock'), DeclareLaunchArgument( 'description_format', - default_value='urdf', + default_value='sdf', description='Robot description format to use, urdf or sdf'), ]) ld.add_action(OpaqueFunction(function=robot_state_publisher)) diff --git a/gz_ros2_control_demos/launch/diff_drive_example_namespaced.launch.py b/gz_ros2_control_demos/launch/diff_drive_example_namespaced.launch.py index 7ce742be..6d1058df 100644 --- a/gz_ros2_control_demos/launch/diff_drive_example_namespaced.launch.py +++ b/gz_ros2_control_demos/launch/diff_drive_example_namespaced.launch.py @@ -12,59 +12,51 @@ # See the License for the specific language governing permissions and # limitations under the License. +from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription -from launch.actions import RegisterEventHandler -from launch.event_handlers import OnProcessExit -from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.actions import DeclareLaunchArgument, ExecuteProcess, OpaqueFunction from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution - from launch_ros.actions import Node -from launch_ros.substitutions import FindPackageShare +from ros_gz_bridge.actions import RosGzBridge +from ros_gz_sim.actions import GzServer def generate_launch_description(): + pkg_share = get_package_share_directory('gz_ros2_control_demos') + # Launch Arguments use_sim_time = LaunchConfiguration('use_sim_time', default=True) - # Get URDF via xacro - robot_description_content = Command( - [ - PathJoinSubstitution([FindExecutable(name='xacro')]), - ' ', - PathJoinSubstitution( - [FindPackageShare('gz_ros2_control_demos'), - 'urdf', 'test_diff_drive.xacro.urdf'] - ), - ' ', - 'namespace:=r1', - ] - ) - robot_description = {'robot_description': robot_description_content} - robot_controllers = PathJoinSubstitution( - [ - FindPackageShare('gz_ros2_control_demos'), - 'config', - 'diff_drive_controller.yaml', - ] - ) - - node_robot_state_publisher = Node( - package='robot_state_publisher', - executable='robot_state_publisher', - namespace='r1', - output='screen', - parameters=[robot_description] - ) + def robot_state_publisher(context): + description_format = LaunchConfiguration('description_format').perform(context) + # Get URDF or SDF via xacro + xacro_processed = Command( + [ + PathJoinSubstitution([FindExecutable(name='xacro')]), + ' ', + PathJoinSubstitution([ + pkg_share, + description_format, + f'test_diff_drive.xacro.{description_format}' + ]), + ' ', + 'namespace:=r1', + ] + ) + node_robot_state_publisher = Node( + package='robot_state_publisher', + executable='robot_state_publisher', + namespace='r1', + output='screen', + parameters=[{'robot_description': xacro_processed}] + ) + return [node_robot_state_publisher] - gz_spawn_entity = Node( - package='ros_gz_sim', - executable='create', - namespace='r1', - output='screen', - arguments=['-topic', 'robot_description', '-name', - 'diff_drive', '-allow_renaming', 'true'], - ) + robot_controllers = PathJoinSubstitution([ + pkg_share, + 'config', + 'diff_drive_controller.yaml' + ]) joint_state_broadcaster_spawner = Node( package='controller_manager', @@ -76,51 +68,62 @@ def generate_launch_description(): '-r /r1/diff_drive_controller/cmd_vel:=/r1/cmd_vel', ], ) - diff_drive_base_controller_spawner = Node( + diff_drive_controller_spawner = Node( package='controller_manager', executable='spawner', arguments=[ - 'diff_drive_base_controller', + 'diff_drive_controller', '--param-file', robot_controllers, '-c', '/r1/controller_manager' - ], + ], ) - # Bridge - bridge = Node( - package='ros_gz_bridge', - executable='parameter_bridge', - arguments=['/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock'], - output='screen' + # Launch just the Gazebo server as a composable node. + gz_server = GzServer( + world_sdf_file='empty.sdf', + container_name='ros_gz_container', + create_own_container='True', + use_composition='True', ) - return LaunchDescription([ - # Launch gazebo environment - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [PathJoinSubstitution([FindPackageShare('ros_gz_sim'), - 'launch', - 'gz_sim.launch.py'])]), - launch_arguments=[('gz_args', [' -r -v 1 empty.sdf'])]), - RegisterEventHandler( - event_handler=OnProcessExit( - target_action=gz_spawn_entity, - on_exit=[joint_state_broadcaster_spawner], - ) - ), - RegisterEventHandler( - event_handler=OnProcessExit( - target_action=joint_state_broadcaster_spawner, - on_exit=[diff_drive_base_controller_spawner], - ) - ), - bridge, - node_robot_state_publisher, + gz_gui = ExecuteProcess(cmd=['gz', 'sim', '-g'], output='screen') + + gz_spawn_entity = Node( + package='ros_gz_sim', + executable='create', + namespace='r1', + output='screen', + arguments=['-topic', 'robot_description', '-name', + 'diff_drive', '-allow_renaming', 'true'], + ) + + # Setup ros_gz_bridge to bridge topics between ROS and Gazebo. + # It is launched as a composable node in the container created by the Gazebo server. + ros_gz_bridge = RosGzBridge( + bridge_name='ros_gz_bridge', + config_file=PathJoinSubstitution([pkg_share, 'config', 'ros_gz_bridge_config.yaml']), + container_name='ros_gz_container', + create_own_container='False', + use_composition='True', + ) + + ld = LaunchDescription([ + gz_server, + gz_gui, gz_spawn_entity, + ros_gz_bridge, + joint_state_broadcaster_spawner, + diff_drive_controller_spawner, # Launch Arguments DeclareLaunchArgument( 'use_sim_time', default_value=use_sim_time, description='If true, use simulated clock'), + DeclareLaunchArgument( + 'description_format', + default_value='sdf', + description='Robot description format to use, urdf or sdf'), ]) + ld.add_action(OpaqueFunction(function=robot_state_publisher)) + return ld diff --git a/gz_ros2_control_demos/package.xml b/gz_ros2_control_demos/package.xml index 80be55d4..6f54712a 100644 --- a/gz_ros2_control_demos/package.xml +++ b/gz_ros2_control_demos/package.xml @@ -20,6 +20,8 @@ geometry_msgs rclcpp rclcpp_action + ros_gz_bridge + ros_gz_sim std_msgs ament_index_python diff --git a/gz_ros2_control_demos/sdf/test_ackermann_drive.xacro.sdf b/gz_ros2_control_demos/sdf/test_ackermann_drive.xacro.sdf index bca986df..dad59a2e 100644 --- a/gz_ros2_control_demos/sdf/test_ackermann_drive.xacro.sdf +++ b/gz_ros2_control_demos/sdf/test_ackermann_drive.xacro.sdf @@ -359,6 +359,12 @@ $(find gz_ros2_control_demos)/config/ackermann_drive_controller.yaml + + + ackermann_drive/odom + ackermann_drive + /gz/odom + diff --git a/gz_ros2_control_demos/sdf/test_diff_drive.xacro.sdf b/gz_ros2_control_demos/sdf/test_diff_drive.xacro.sdf index 0e7f2bad..fc3f9b64 100644 --- a/gz_ros2_control_demos/sdf/test_diff_drive.xacro.sdf +++ b/gz_ros2_control_demos/sdf/test_diff_drive.xacro.sdf @@ -1,6 +1,9 @@ + + + true @@ -212,7 +215,16 @@ - $(find gz_ros2_control_demos)/config/diff_drive_controller_velocity.yaml + + $(arg namespace) + + $(find gz_ros2_control_demos)/config/diff_drive_controller.yaml + + + + diff_drive/odom + diff_drive + /gz/odom diff --git a/gz_ros2_control_demos/urdf/test_ackermann_drive.xacro.urdf b/gz_ros2_control_demos/urdf/test_ackermann_drive.xacro.urdf index bcdf8205..26f53293 100644 --- a/gz_ros2_control_demos/urdf/test_ackermann_drive.xacro.urdf +++ b/gz_ros2_control_demos/urdf/test_ackermann_drive.xacro.urdf @@ -216,6 +216,12 @@ $(find gz_ros2_control_demos)/config/ackermann_drive_controller.yaml + + + ackermann_drive/odom + ackermann_drive + /gz/odom + diff --git a/gz_ros2_control_demos/urdf/test_diff_drive.xacro.urdf b/gz_ros2_control_demos/urdf/test_diff_drive.xacro.urdf index 08956f33..98c5f75e 100644 --- a/gz_ros2_control_demos/urdf/test_diff_drive.xacro.urdf +++ b/gz_ros2_control_demos/urdf/test_diff_drive.xacro.urdf @@ -176,6 +176,12 @@ $(find gz_ros2_control_demos)/config/diff_drive_controller.yaml + + + diff_drive/odom + diff_drive + /gz/odom +