diff --git a/README.md b/README.md index a2cf24e2..44712ff9 100644 --- a/README.md +++ b/README.md @@ -3,27 +3,26 @@ ROS 2 version | Gazebo version | Branch | Binaries hosted at -- | -- | -- | -- Foxy | Citadel | [foxy](https://github.com/gazebosim/ros_gz/tree/foxy) | https://packages.ros.org -Foxy | Edifice | [foxy](https://github.com/gazebosim/ros_gz/tree/foxy) | only from source -Galactic | Edifice | [galactic](https://github.com/gazebosim/ros_gz/tree/galactic) | https://packages.ros.org +Foxy | Edifice | [foxy](https://github.com/gazebosim/ros_gz/tree/foxy) | only from source [^2] +Galactic | Edifice | [galactic](https://github.com/gazebosim/ros_gz/tree/galactic) | https://packages.ros.org [^2] Galactic | Fortress | [galactic](https://github.com/gazebosim/ros_gz/tree/galactic) | only from source Humble | Fortress | [humble](https://github.com/gazebosim/ros_gz/tree/humble) | https://packages.ros.org -Humble | Garden | [humble](https://github.com/gazebosim/ros_gz/tree/humble) | [gazebo packages](https://gazebosim.org/docs/latest/ros_installation#gazebo-garden-with-ros-2-humble-iron-or-rolling-use-with-caution-)[^1] +Humble | Garden | [humble](https://github.com/gazebosim/ros_gz/tree/humble) | [gazebo packages](https://gazebosim.org/docs/latest/ros_installation#gazebo-garden-with-ros-2-humble-iron-or-rolling-use-with-caution-)[^1] [^2] Humble | Harmonic | [humble](https://github.com/gazebosim/ros_gz/tree/humble) | [gazebo packages](https://gazebosim.org/docs/harmonic/ros_installation#-gazebo-harmonic-with-ros-2-humble-iron-or-rolling-use-with-caution-)[^1] Iron | Fortress | [humble](https://github.com/gazebosim/ros_gz/tree/iron) | https://packages.ros.org -Iron | Garden | [humble](https://github.com/gazebosim/ros_gz/tree/iron) | only from source +Iron | Garden | [humble](https://github.com/gazebosim/ros_gz/tree/iron) | only from source [^2] Iron | Harmonic | [humble](https://github.com/gazebosim/ros_gz/tree/iron) | only from source -Jazzy | Garden | [ros2](https://github.com/gazebosim/ros_gz/tree/ros2) | only from source +Jazzy | Garden | [ros2](https://github.com/gazebosim/ros_gz/tree/ros2) | only from source [^2] Jazzy | Harmonic | [jazzy](https://github.com/gazebosim/ros_gz/tree/jazzy) | https://packages.ros.org Rolling | Fortress | [humble](https://github.com/gazebosim/ros_gz/tree/humble) | https://packages.ros.org -Rolling | Garden | [ros2](https://github.com/gazebosim/ros_gz/tree/ros2) | only from source +Rolling | Garden | [ros2](https://github.com/gazebosim/ros_gz/tree/ros2) | only from source [^2] Rolling | Harmonic | [ros2](https://github.com/gazebosim/ros_gz/tree/ros2) | only from source -[^1]: Binaries for these pairings are provided from a the packages.osrfoundation.org repository. Refer to https://gazebosim.org/docs/latest/ros_installation for installation instructions. +[^1]: Binaries for these pairings are provided from the packages.osrfoundation.org repository. Refer to https://gazebosim.org/docs/latest/ros_installation for installation instructions. +[^2]: Note that the Gazebo version on this row has reached end-of-life. For information on ROS(1) and Gazebo compatibility, refer to the [noetic branch README](https://github.com/gazebosim/ros_gz/tree/noetic) -> Please [ticket an issue](https://github.com/gazebosim/ros_gz/issues/) if you'd like support to be added for some combination. - [Details about the renaming process](README_RENAME.md) from `ign` to `gz` . **Note**: The `ros_ign` prefixed packages are shim packages that redirect to their `ros_gz` counterpart. @@ -86,7 +85,7 @@ Be sure you've installed #### Gazebo -Install either [Fortress, Garden, or Harmonic](https://gazebosim.org/docs). +Install either [Fortress, Harmonic or Ionic](https://gazebosim.org/docs). Set the `GZ_VERSION` environment variable to the Gazebo version you'd like to compile against. For example: @@ -97,7 +96,7 @@ like to compile against. For example: #### Compile ros_gz -The following steps are for Linux and OSX. +The following steps are for Linux and macOS. 1. Create a colcon workspace: diff --git a/ros_gz/CHANGELOG.rst b/ros_gz/CHANGELOG.rst index d2bd485c..9effb7fc 100644 --- a/ros_gz/CHANGELOG.rst +++ b/ros_gz/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package ros_gz ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +2.1.2 (2024-10-31) +------------------ + 2.1.1 (2024-10-14) ------------------ diff --git a/ros_gz/package.xml b/ros_gz/package.xml index d96e8895..585a4754 100644 --- a/ros_gz/package.xml +++ b/ros_gz/package.xml @@ -4,7 +4,7 @@ ros_gz - 2.1.1 + 2.1.2 Meta-package containing interfaces for using ROS 2 with Gazebo simulation. Aditya Pande Alejandro Hernandez diff --git a/ros_gz_bridge/CHANGELOG.rst b/ros_gz_bridge/CHANGELOG.rst index ab9c1627..81ce3d08 100644 --- a/ros_gz_bridge/CHANGELOG.rst +++ b/ros_gz_bridge/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package ros_gz_bridge ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +2.1.2 (2024-10-31) +------------------ + 2.1.1 (2024-10-14) ------------------ * Extra parameter to start a container (`#616 `_) diff --git a/ros_gz_bridge/launch/ros_gz_bridge.launch b/ros_gz_bridge/launch/ros_gz_bridge.launch index fa76221e..96757e4e 100644 --- a/ros_gz_bridge/launch/ros_gz_bridge.launch +++ b/ros_gz_bridge/launch/ros_gz_bridge.launch @@ -7,6 +7,7 @@ + + log_level="$(var log_level)" + bridge_params="$(var bridge_params)"> diff --git a/ros_gz_bridge/launch/ros_gz_bridge.launch.py b/ros_gz_bridge/launch/ros_gz_bridge.launch.py index 3e52dee6..e1690a84 100644 --- a/ros_gz_bridge/launch/ros_gz_bridge.launch.py +++ b/ros_gz_bridge/launch/ros_gz_bridge.launch.py @@ -15,24 +15,13 @@ """Launch ros_gz bridge in a component container.""" from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, GroupAction -from launch.conditions import IfCondition -from launch.substitutions import LaunchConfiguration, PythonExpression -from launch_ros.actions import ComposableNodeContainer, LoadComposableNodes, Node -from launch_ros.descriptions import ComposableNode +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration +from ros_gz_bridge.actions import RosGzBridge def generate_launch_description(): - bridge_name = LaunchConfiguration('bridge_name') - config_file = LaunchConfiguration('config_file') - container_name = LaunchConfiguration('container_name') - create_own_container = LaunchConfiguration('create_own_container') - namespace = LaunchConfiguration('namespace') - use_composition = LaunchConfiguration('use_composition') - use_respawn = LaunchConfiguration('use_respawn') - log_level = LaunchConfiguration('log_level') - declare_bridge_name_cmd = DeclareLaunchArgument( 'bridge_name', description='Name of ros_gz_bridge node' ) @@ -74,57 +63,20 @@ def generate_launch_description(): 'log_level', default_value='info', description='log level' ) - load_nodes = GroupAction( - condition=IfCondition(PythonExpression(['not ', use_composition])), - actions=[ - Node( - package='ros_gz_bridge', - executable='bridge_node', - name=bridge_name, - namespace=namespace, - output='screen', - respawn=use_respawn, - respawn_delay=2.0, - parameters=[{'config_file': config_file}], - arguments=['--ros-args', '--log-level', log_level], - ), - ], - ) - - load_composable_nodes_with_container = ComposableNodeContainer( - condition=IfCondition( - PythonExpression([use_composition, ' and ', create_own_container])), - name=LaunchConfiguration('container_name'), - namespace='', - package='rclcpp_components', - executable='component_container', - composable_node_descriptions=[ - ComposableNode( - package='ros_gz_bridge', - plugin='ros_gz_bridge::RosGzBridge', - name=bridge_name, - namespace=namespace, - parameters=[{'config_file': config_file}], - extra_arguments=[{'use_intra_process_comms': True}], - ), - ], - output='screen', + declare_bridge_params_cmd = DeclareLaunchArgument( + 'bridge_params', default_value='', description='Extra parameters to pass to the bridge.' ) - load_composable_nodes_without_container = LoadComposableNodes( - condition=IfCondition( - PythonExpression([use_composition, ' and not ', create_own_container])), - target_container=container_name, - composable_node_descriptions=[ - ComposableNode( - package='ros_gz_bridge', - plugin='ros_gz_bridge::RosGzBridge', - name=bridge_name, - namespace=namespace, - parameters=[{'config_file': config_file}], - extra_arguments=[{'use_intra_process_comms': True}], - ), - ], + ros_gz_bridge_action = RosGzBridge( + bridge_name=LaunchConfiguration('bridge_name'), + config_file=LaunchConfiguration('config_file'), + container_name=LaunchConfiguration('container_name'), + create_own_container=LaunchConfiguration('create_own_container'), + namespace=LaunchConfiguration('namespace'), + use_composition=LaunchConfiguration('use_composition'), + use_respawn=LaunchConfiguration('use_respawn'), + log_level=LaunchConfiguration('log_level'), + bridge_params=LaunchConfiguration('bridge_params') ) # Create the launch description and populate @@ -139,9 +91,7 @@ def generate_launch_description(): ld.add_action(declare_use_composition_cmd) ld.add_action(declare_use_respawn_cmd) ld.add_action(declare_log_level_cmd) - # Add the actions to launch all of the bridge nodes - ld.add_action(load_nodes) - ld.add_action(load_composable_nodes_with_container) - ld.add_action(load_composable_nodes_without_container) - + ld.add_action(declare_bridge_params_cmd) + # Add the ros_gz_bridge action + ld.add_action(ros_gz_bridge_action) return ld diff --git a/ros_gz_bridge/package.xml b/ros_gz_bridge/package.xml index f95cfa22..b43b0cd7 100644 --- a/ros_gz_bridge/package.xml +++ b/ros_gz_bridge/package.xml @@ -2,7 +2,7 @@ ros_gz_bridge - 2.1.1 + 2.1.2 Bridge communication between ROS and Gazebo Transport Aditya Pande Alejandro Hernandez diff --git a/ros_gz_bridge/ros_gz_bridge/actions/ros_gz_bridge.py b/ros_gz_bridge/ros_gz_bridge/actions/ros_gz_bridge.py index 8459de4f..ee28a2f4 100644 --- a/ros_gz_bridge/ros_gz_bridge/actions/ros_gz_bridge.py +++ b/ros_gz_bridge/ros_gz_bridge/actions/ros_gz_bridge.py @@ -14,17 +14,18 @@ """Module for the ros_gz bridge action.""" -from typing import List -from typing import Optional +from typing import cast, Dict, List, Optional, Union from launch.action import Action -from launch.actions import IncludeLaunchDescription from launch.frontend import Entity, expose_action, Parser from launch.launch_context import LaunchContext -from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.some_substitutions_type import SomeSubstitutionsType -from launch.substitutions import PathJoinSubstitution -from launch_ros.substitutions import FindPackageShare +from launch.substitutions import TextSubstitution +from launch.utilities import ensure_argument_type +from launch.utilities.type_utils import normalize_typed_substitution, perform_typed_substitution +from launch_ros.actions import ComposableNodeContainer, LoadComposableNodes, Node +from launch_ros.descriptions import ComposableNode +from launch_ros.parameters_type import SomeParameters @expose_action('ros_gz_bridge') @@ -36,20 +37,18 @@ def __init__( *, bridge_name: SomeSubstitutionsType, config_file: SomeSubstitutionsType, - container_name: Optional[SomeSubstitutionsType] = 'ros_gz_container', - create_own_container: Optional[SomeSubstitutionsType] = 'False', - namespace: Optional[SomeSubstitutionsType] = '', - use_composition: Optional[SomeSubstitutionsType] = 'False', - use_respawn: Optional[SomeSubstitutionsType] = 'False', - log_level: Optional[SomeSubstitutionsType] = 'info', + container_name: SomeSubstitutionsType = 'ros_gz_container', + create_own_container: Union[bool, SomeSubstitutionsType] = False, + namespace: SomeSubstitutionsType = '', + use_composition: Union[bool, SomeSubstitutionsType] = False, + use_respawn: Union[bool, SomeSubstitutionsType] = False, + log_level: SomeSubstitutionsType = 'info', + bridge_params: Optional[SomeParameters] = None, **kwargs ) -> None: """ Construct a ros_gz bridge action. - All arguments are forwarded to `ros_gz_bridge.launch.ros_gz_bridge.launch.py`, - so see the documentation of that class for further details. - :param: bridge_name Name of ros_gz_bridge node :param: config_file YAML config file. :param: container_name Name of container that nodes will load in if use composition. @@ -58,21 +57,52 @@ def __init__( :param: use_composition Use composed bringup if True. :param: use_respawn Whether to respawn if a node crashes (when composition is disabled). :param: log_level Log level. + :param: bridge_params Extra parameters to pass to the bridge. """ super().__init__(**kwargs) + self.__bridge_name = bridge_name self.__config_file = config_file self.__container_name = container_name - self.__create_own_container = create_own_container self.__namespace = namespace - self.__use_composition = use_composition - self.__use_respawn = use_respawn + + # This is here to allow using strings or booleans as values for boolean variables when + # the Action is used from Python i.e., this allows users to do: + # RosGzBridge(bridge_name='bridge1', use_composition='true', create_own_container=True) + # Note that use_composition is set to a string while create_own_container is set to a + # boolean. The reverse would also work. + # At some point, we might want to deprecate this and only allow setting booleans since + # that's what users would expect when calling this from Python + if isinstance(create_own_container, str): + self.__create_own_container = normalize_typed_substitution( + TextSubstitution(text=create_own_container), bool + ) + else: + self.__create_own_container = normalize_typed_substitution( + create_own_container, bool + ) + + if isinstance(use_composition, str): + self.__use_composition = normalize_typed_substitution( + TextSubstitution(text=use_composition), bool + ) + else: + self.__use_composition = normalize_typed_substitution(use_composition, bool) + + self.__use_respawn = normalize_typed_substitution(use_respawn, bool) self.__log_level = log_level + self.__bridge_params = [{'config_file': self.__config_file}] + if bridge_params is not None: + # This handling of bridge_params was copied from launch_ros/actions/node.py + ensure_argument_type(bridge_params, (list), 'bridge_params', 'RosGzBridge') + # All elements in the list are paths to files with parameters (or substitutions that + # evaluate to paths), or dictionaries of parameters (fields can be substitutions). + self.__bridge_params.extend(cast(list, bridge_params)) @classmethod def parse(cls, entity: Entity, parser: Parser): """Parse ros_gz_bridge.""" - _, kwargs = super().parse(entity, parser) + kwargs: Dict = super().parse(entity, parser)[1] bridge_name = entity.get_attr( 'bridge_name', data_type=str, @@ -106,6 +136,8 @@ def parse(cls, entity: Entity, parser: Parser): 'log_level', data_type=str, optional=True) + parameters = entity.get_attr('param', data_type=List[Entity], optional=True) + if isinstance(bridge_name, str): bridge_name = parser.parse_substitution(bridge_name) kwargs['bridge_name'] = bridge_name @@ -139,22 +171,70 @@ def parse(cls, entity: Entity, parser: Parser): log_level = parser.parse_substitution(log_level) kwargs['log_level'] = log_level + if parameters is not None: + kwargs['bridge_params'] = Node.parse_nested_parameters(parameters, parser) + return cls, kwargs def execute(self, context: LaunchContext) -> Optional[List[Action]]: """Execute the action.""" - ros_gz_bridge_description = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [PathJoinSubstitution([FindPackageShare('ros_gz_bridge'), - 'launch', - 'ros_gz_bridge.launch.py'])]), - launch_arguments=[('bridge_name', self.__bridge_name), - ('config_file', self.__config_file), - ('container_name', self.__container_name), - ('create_own_container', self.__create_own_container), - ('namespace', self.__namespace), - ('use_composition', self.__use_composition), - ('use_respawn', self.__use_respawn), - ('log_level', self.__log_level), ]) - - return [ros_gz_bridge_description] + use_composition_eval = perform_typed_substitution( + context, self.__use_composition, bool + ) + create_own_container_eval = perform_typed_substitution( + context, self.__create_own_container, bool + ) + + launch_descriptions: List[Action] = [] + + if not use_composition_eval: + # Standard node configuration + launch_descriptions.append(Node( + package='ros_gz_bridge', + executable='bridge_node', + name=self.__bridge_name, + namespace=self.__namespace, + output='screen', + respawn=perform_typed_substitution(context, self.__use_respawn, bool), + respawn_delay=2.0, + parameters=self.__bridge_params, + arguments=['--ros-args', '--log-level', self.__log_level], + )) + + # Composable node with container configuration + if use_composition_eval and create_own_container_eval: + launch_descriptions.append(ComposableNodeContainer( + name=self.__container_name, + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + ComposableNode( + package='ros_gz_bridge', + plugin='ros_gz_bridge::RosGzBridge', + name=self.__bridge_name, + namespace=self.__namespace, + parameters=self.__bridge_params, + extra_arguments=[{'use_intra_process_comms': True}], + ), + ], + output='screen', + )) + + # Composable node without container configuration + if use_composition_eval and not create_own_container_eval: + launch_descriptions.append(LoadComposableNodes( + target_container=self.__container_name, + composable_node_descriptions=[ + ComposableNode( + package='ros_gz_bridge', + plugin='ros_gz_bridge::RosGzBridge', + name=self.__bridge_name, + namespace=self.__namespace, + parameters=self.__bridge_params, + extra_arguments=[{'use_intra_process_comms': True}], + ), + ], + )) + + return launch_descriptions diff --git a/ros_gz_image/CHANGELOG.rst b/ros_gz_image/CHANGELOG.rst index fdefc560..1504e16c 100644 --- a/ros_gz_image/CHANGELOG.rst +++ b/ros_gz_image/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package ros1_ign_image ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +2.1.2 (2024-10-31) +------------------ + 2.1.1 (2024-10-14) ------------------ diff --git a/ros_gz_image/package.xml b/ros_gz_image/package.xml index 36d33fae..e6961941 100644 --- a/ros_gz_image/package.xml +++ b/ros_gz_image/package.xml @@ -1,6 +1,6 @@ ros_gz_image - 2.1.1 + 2.1.2 Image utilities for Gazebo simulation with ROS. Apache 2.0 Aditya Pande diff --git a/ros_gz_interfaces/CHANGELOG.rst b/ros_gz_interfaces/CHANGELOG.rst index 7d192134..db0253ff 100644 --- a/ros_gz_interfaces/CHANGELOG.rst +++ b/ros_gz_interfaces/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package ros_gz_interfaces ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +2.1.2 (2024-10-31) +------------------ + 2.1.1 (2024-10-14) ------------------ diff --git a/ros_gz_interfaces/package.xml b/ros_gz_interfaces/package.xml index d9ab3282..c6aeb753 100644 --- a/ros_gz_interfaces/package.xml +++ b/ros_gz_interfaces/package.xml @@ -1,6 +1,6 @@ ros_gz_interfaces - 2.1.1 + 2.1.2 Message and service data structures for interacting with Gazebo from ROS2. Apache 2.0 Louise Poubel diff --git a/ros_gz_sim/CHANGELOG.rst b/ros_gz_sim/CHANGELOG.rst index 05a7e81b..4ad7fd41 100644 --- a/ros_gz_sim/CHANGELOG.rst +++ b/ros_gz_sim/CHANGELOG.rst @@ -2,6 +2,14 @@ Changelog for package ros_gz_sim ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +2.1.2 (2024-10-31) +------------------ +* Create ros_gz_spawn_model.launch (`#604 `_) + Co-authored-by: Alejandro Hernández Cordero +* Add create_own_container argument to ros_gz_spawn_model.launch.py (`#622 `_) +* Fix ros_gz_sim.launch.py when create_own_container is enabled. (`#620 `_) +* Contributors: Aarav Gupta, Amronos, Carlos Agüero + 2.1.1 (2024-10-14) ------------------ * Extra parameter to start a container (`#616 `_) diff --git a/ros_gz_sim/CMakeLists.txt b/ros_gz_sim/CMakeLists.txt index dc21f8a9..e61622f9 100644 --- a/ros_gz_sim/CMakeLists.txt +++ b/ros_gz_sim/CMakeLists.txt @@ -48,7 +48,17 @@ target_link_libraries(create gz-msgs::core gz-transport::core ) -ament_target_dependencies(create std_msgs) + +add_executable(remove src/remove.cpp) +ament_target_dependencies(remove + rclcpp + std_msgs +) + +target_link_libraries(remove + gz-msgs::core + gz-transport::core +) add_library(${PROJECT_NAME} SHARED src/Stopwatch.cpp) ament_target_dependencies(${PROJECT_NAME} @@ -104,13 +114,16 @@ install(FILES "launch/ros_gz_sim.launch" "launch/ros_gz_sim.launch.py" "launch/ros_gz_spawn_model.launch.py" + "launch/gz_remove_model.launch.py" DESTINATION share/${PROJECT_NAME}/launch ) install(TARGETS create + remove DESTINATION lib/${PROJECT_NAME} ) + install(TARGETS gzserver DESTINATION lib/${PROJECT_NAME} @@ -151,6 +164,10 @@ if(BUILD_TESTING) test/test_create.cpp ) + ament_add_gtest_executable(test_remove + test/test_remove.cpp + ) + ament_target_dependencies(test_stopwatch rclcpp) target_include_directories(test_stopwatch PUBLIC @@ -165,12 +182,17 @@ if(BUILD_TESTING) gz-transport::core ) + target_link_libraries(test_remove + gz-transport::core + ) + install( - TARGETS test_stopwatch test_create + TARGETS test_stopwatch test_create test_remove DESTINATION lib/${PROJECT_NAME} ) ament_add_gtest_test(test_stopwatch) add_launch_test(test/test_create_node.launch.py TIMEOUT 200) + add_launch_test(test/test_remove_node.launch.py TIMEOUT 200) endif() ament_package() diff --git a/ros_gz_sim/launch/gz_remove_model.launch.py b/ros_gz_sim/launch/gz_remove_model.launch.py new file mode 100644 index 00000000..14dd643a --- /dev/null +++ b/ros_gz_sim/launch/gz_remove_model.launch.py @@ -0,0 +1,50 @@ +# Copyright 2024 Open Source Robotics Foundation, Inc. +# +# 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. + +"""Launch remove models in gz sim.""" + +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration, TextSubstitution +from launch_ros.actions import Node + + +def generate_launch_description(): + + world = LaunchConfiguration('world') + entity_name = LaunchConfiguration('entity_name') + + declare_world_cmd = DeclareLaunchArgument( + 'world', default_value=TextSubstitution(text=''), + description='World name') + declare_entity_name_cmd = DeclareLaunchArgument( + 'entity_name', default_value=TextSubstitution(text=''), + description='SDF filename') + + remove = Node( + package='ros_gz_sim', + executable='remove', + output='screen', + parameters=[{'world': world, 'entity_name': entity_name}], + ) + + # Create the launch description and populate + ld = LaunchDescription() + + # Declare the launch options + ld.add_action(declare_world_cmd) + ld.add_action(declare_entity_name_cmd) + ld.add_action(remove) + + return ld diff --git a/ros_gz_sim/launch/gz_server.launch.py b/ros_gz_sim/launch/gz_server.launch.py index 70de5d24..3c231a1d 100644 --- a/ros_gz_sim/launch/gz_server.launch.py +++ b/ros_gz_sim/launch/gz_server.launch.py @@ -16,10 +16,8 @@ from launch import LaunchDescription from launch.actions import DeclareLaunchArgument -from launch.conditions import IfCondition -from launch.substitutions import LaunchConfiguration, PythonExpression, TextSubstitution -from launch_ros.actions import ComposableNodeContainer, LoadComposableNodes, Node -from launch_ros.descriptions import ComposableNode +from launch.substitutions import LaunchConfiguration, TextSubstitution +from ros_gz_sim.actions import GzServer def generate_launch_description(): @@ -40,51 +38,12 @@ def generate_launch_description(): 'use_composition', default_value='False', description='Use composed bringup if True') - load_nodes = Node( - condition=IfCondition(PythonExpression(['not ', LaunchConfiguration('use_composition')])), - package='ros_gz_sim', - executable='gzserver', - output='screen', - parameters=[{'world_sdf_file': LaunchConfiguration('world_sdf_file'), - 'world_sdf_string': LaunchConfiguration('world_sdf_string')}], - ) - - load_composable_nodes_with_container = ComposableNodeContainer( - condition=IfCondition( - PythonExpression([LaunchConfiguration('use_composition'), ' and ', - LaunchConfiguration('create_own_container')])), - name=LaunchConfiguration('container_name'), - namespace='', - package='rclcpp_components', - executable='component_container', - composable_node_descriptions=[ - ComposableNode( - package='ros_gz_sim', - plugin='ros_gz_sim::GzServer', - name='gz_server', - parameters=[{'world_sdf_file': LaunchConfiguration('world_sdf_file'), - 'world_sdf_string': LaunchConfiguration('world_sdf_string')}], - extra_arguments=[{'use_intra_process_comms': True}], - ), - ], - output='screen', - ) - - load_composable_nodes_without_container = LoadComposableNodes( - condition=IfCondition( - PythonExpression([LaunchConfiguration('use_composition'), ' and not ', - LaunchConfiguration('create_own_container')])), - target_container=LaunchConfiguration('container_name'), - composable_node_descriptions=[ - ComposableNode( - package='ros_gz_sim', - plugin='ros_gz_sim::GzServer', - name='gz_server', - parameters=[{'world_sdf_file': LaunchConfiguration('world_sdf_file'), - 'world_sdf_string': LaunchConfiguration('world_sdf_string')}], - extra_arguments=[{'use_intra_process_comms': True}], - ), - ], + gz_server_action = GzServer( + world_sdf_file=LaunchConfiguration('world_sdf_file'), + world_sdf_string=LaunchConfiguration('world_sdf_string'), + container_name=LaunchConfiguration('container_name'), + create_own_container=LaunchConfiguration('create_own_container'), + use_composition=LaunchConfiguration('use_composition'), ) # Create the launch description and populate @@ -96,9 +55,7 @@ def generate_launch_description(): ld.add_action(declare_container_name_cmd) ld.add_action(declare_create_own_container_cmd) ld.add_action(declare_use_composition_cmd) - # Add the actions to launch all of the gz_server nodes - ld.add_action(load_nodes) - ld.add_action(load_composable_nodes_with_container) - ld.add_action(load_composable_nodes_without_container) + # Add the gz_server action + ld.add_action(gz_server_action) return ld diff --git a/ros_gz_sim/launch/ros_gz_sim.launch b/ros_gz_sim/launch/ros_gz_sim.launch index 6130ce0d..e39f10b6 100644 --- a/ros_gz_sim/launch/ros_gz_sim.launch +++ b/ros_gz_sim/launch/ros_gz_sim.launch @@ -7,6 +7,7 @@ + + log_level="$(var log_level)" + bridge_params="$(var bridge_params)"> diff --git a/ros_gz_sim/launch/ros_gz_sim.launch.py b/ros_gz_sim/launch/ros_gz_sim.launch.py index aba047ee..1465c44e 100644 --- a/ros_gz_sim/launch/ros_gz_sim.launch.py +++ b/ros_gz_sim/launch/ros_gz_sim.launch.py @@ -15,26 +15,14 @@ """Launch gzsim + ros_gz_bridge in a component container.""" from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration, PathJoinSubstitution, TextSubstitution -from launch_ros.substitutions import FindPackageShare +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration, TextSubstitution +from ros_gz_bridge.actions import RosGzBridge +from ros_gz_sim.actions import GzServer def generate_launch_description(): - bridge_name = LaunchConfiguration('bridge_name') - config_file = LaunchConfiguration('config_file') - container_name = LaunchConfiguration('container_name') - create_own_container = LaunchConfiguration('create_own_container') - namespace = LaunchConfiguration('namespace') - use_composition = LaunchConfiguration('use_composition') - use_respawn = LaunchConfiguration('use_respawn') - bridge_log_level = LaunchConfiguration('bridge_log_level') - - world_sdf_file = LaunchConfiguration('world_sdf_file') - world_sdf_string = LaunchConfiguration('world_sdf_string') - declare_bridge_name_cmd = DeclareLaunchArgument( 'bridge_name', description='Name of the bridge' ) @@ -73,6 +61,10 @@ def generate_launch_description(): 'bridge_log_level', default_value='info', description='Bridge log level' ) + declare_bridge_params_cmd = DeclareLaunchArgument( + 'bridge_params', default_value='', description='Extra parameters to pass to the bridge.' + ) + declare_world_sdf_file_cmd = DeclareLaunchArgument( 'world_sdf_file', default_value=TextSubstitution(text=''), description='Path to the SDF world file' @@ -83,30 +75,25 @@ def generate_launch_description(): description='SDF world string' ) - gz_server_description = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [PathJoinSubstitution([FindPackageShare('ros_gz_sim'), - 'launch', - 'gz_server.launch.py'])]), - launch_arguments=[('world_sdf_file', world_sdf_file), - ('world_sdf_string', world_sdf_string), - ('container_name', container_name), - ('create_own_container', create_own_container), - ('use_composition', use_composition), ]) - - bridge_description = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [PathJoinSubstitution([FindPackageShare('ros_gz_bridge'), - 'launch', - 'ros_gz_bridge.launch.py'])]), - launch_arguments=[('bridge_name', bridge_name), - ('config_file', config_file), - ('container_name', container_name), - ('namespace', namespace), - ('create_own_container', str(False)), - ('use_composition', use_composition), - ('use_respawn', use_respawn), - ('bridge_log_level', bridge_log_level), ]) + gz_server_action = GzServer( + world_sdf_file=LaunchConfiguration('world_sdf_file'), + world_sdf_string=LaunchConfiguration('world_sdf_string'), + container_name=LaunchConfiguration('container_name'), + create_own_container=LaunchConfiguration('create_own_container'), + use_composition=LaunchConfiguration('use_composition'), + ) + + ros_gz_bridge_action = RosGzBridge( + bridge_name=LaunchConfiguration('bridge_name'), + config_file=LaunchConfiguration('config_file'), + container_name=LaunchConfiguration('container_name'), + create_own_container=str(False), + namespace=LaunchConfiguration('namespace'), + use_composition=LaunchConfiguration('use_composition'), + use_respawn=LaunchConfiguration('use_respawn'), + log_level=LaunchConfiguration('bridge_log_level'), + bridge_params=LaunchConfiguration('bridge_params'), + ) # Create the launch description and populate ld = LaunchDescription() @@ -120,10 +107,11 @@ def generate_launch_description(): ld.add_action(declare_use_composition_cmd) ld.add_action(declare_use_respawn_cmd) ld.add_action(declare_bridge_log_level_cmd) + ld.add_action(declare_bridge_params_cmd) ld.add_action(declare_world_sdf_file_cmd) ld.add_action(declare_world_sdf_string_cmd) # Add the actions to launch all of the bridge + gz_server nodes - ld.add_action(gz_server_description) - ld.add_action(bridge_description) + ld.add_action(gz_server_action) + ld.add_action(ros_gz_bridge_action) return ld diff --git a/ros_gz_sim/launch/ros_gz_spawn_model.launch b/ros_gz_sim/launch/ros_gz_spawn_model.launch new file mode 100644 index 00000000..e465e7cc --- /dev/null +++ b/ros_gz_sim/launch/ros_gz_spawn_model.launch @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ros_gz_sim/launch/ros_gz_spawn_model.launch.py b/ros_gz_sim/launch/ros_gz_spawn_model.launch.py index e5e20ebd..2ac684fc 100644 --- a/ros_gz_sim/launch/ros_gz_spawn_model.launch.py +++ b/ros_gz_sim/launch/ros_gz_spawn_model.launch.py @@ -19,6 +19,7 @@ from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration, PathJoinSubstitution, TextSubstitution from launch_ros.substitutions import FindPackageShare +from ros_gz_bridge.actions import RosGzBridge def generate_launch_description(): @@ -26,10 +27,12 @@ def generate_launch_description(): bridge_name = LaunchConfiguration('bridge_name') config_file = LaunchConfiguration('config_file') container_name = LaunchConfiguration('container_name') + create_own_container = LaunchConfiguration('create_own_container') namespace = LaunchConfiguration('namespace') use_composition = LaunchConfiguration('use_composition') use_respawn = LaunchConfiguration('use_respawn') log_level = LaunchConfiguration('log_level') + bridge_params = LaunchConfiguration('bridge_params') world = LaunchConfiguration('world') file = LaunchConfiguration('file') @@ -58,12 +61,18 @@ def generate_launch_description(): description='Name of container that nodes will load in if use composition', ) + declare_create_own_container_cmd = DeclareLaunchArgument( + 'create_own_container', + default_value='False', + description='Whether we should start a ROS container when using composition.', + ) + declare_namespace_cmd = DeclareLaunchArgument( 'namespace', default_value='', description='Top-level namespace' ) declare_use_composition_cmd = DeclareLaunchArgument( - 'use_composition', default_value='True', description='Use composed bringup if True' + 'use_composition', default_value='False', description='Use composed bringup if True' ) declare_use_respawn_cmd = DeclareLaunchArgument( @@ -76,6 +85,10 @@ def generate_launch_description(): 'log_level', default_value='info', description='log level' ) + declare_bridge_params_cmd = DeclareLaunchArgument( + 'bridge_params', default_value='', description='Extra parameters to pass to the bridge.' + ) + declare_world_cmd = DeclareLaunchArgument( 'world', default_value=TextSubstitution(text=''), description='World name') @@ -105,18 +118,17 @@ def generate_launch_description(): description='Whether the entity allows renaming or not' ) - bridge_description = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [PathJoinSubstitution([FindPackageShare('ros_gz_bridge'), - 'launch', - 'ros_gz_bridge.launch.py'])]), - launch_arguments=[('bridge_name', bridge_name), - ('config_file', config_file), - ('container_name', container_name), - ('namespace', namespace), - ('use_composition', use_composition), - ('use_respawn', use_respawn), - ('log_level', log_level), ]) + ros_gz_bridge_action = RosGzBridge( + bridge_name=bridge_name, + config_file=config_file, + container_name=container_name, + create_own_container=create_own_container, + namespace=namespace, + use_composition=use_composition, + use_respawn=use_respawn, + log_level=log_level, + bridge_params=bridge_params, + ) spawn_model_description = IncludeLaunchDescription( PythonLaunchDescriptionSource( @@ -143,10 +155,12 @@ def generate_launch_description(): ld.add_action(declare_bridge_name_cmd) ld.add_action(declare_config_file_cmd) ld.add_action(declare_container_name_cmd) + ld.add_action(declare_create_own_container_cmd) ld.add_action(declare_namespace_cmd) ld.add_action(declare_use_composition_cmd) ld.add_action(declare_use_respawn_cmd) ld.add_action(declare_log_level_cmd) + ld.add_action(declare_bridge_params_cmd) ld.add_action(declare_world_cmd) ld.add_action(declare_file_cmd) ld.add_action(declare_model_string_cmd) @@ -154,7 +168,7 @@ def generate_launch_description(): ld.add_action(declare_entity_name_cmd) ld.add_action(declare_allow_renaming_cmd) # Add the actions to launch all of the bridge + spawn_model nodes - ld.add_action(bridge_description) + ld.add_action(ros_gz_bridge_action) ld.add_action(spawn_model_description) return ld diff --git a/ros_gz_sim/package.xml b/ros_gz_sim/package.xml index ac3660ef..d4247f7a 100644 --- a/ros_gz_sim/package.xml +++ b/ros_gz_sim/package.xml @@ -2,7 +2,7 @@ ros_gz_sim - 2.1.1 + 2.1.2 Tools for using Gazebo Sim simulation with ROS. Alejandro Hernandez Aditya Pande diff --git a/ros_gz_sim/ros_gz_sim/actions/gzserver.py b/ros_gz_sim/ros_gz_sim/actions/gzserver.py index 64461baf..7e3ee3d3 100644 --- a/ros_gz_sim/ros_gz_sim/actions/gzserver.py +++ b/ros_gz_sim/ros_gz_sim/actions/gzserver.py @@ -14,17 +14,73 @@ """Module for the GzServer action.""" -from typing import List -from typing import Optional +import os +from typing import Dict, List, Optional, Union +from ament_index_python.packages import get_package_share_directory +from catkin_pkg.package import InvalidPackage, PACKAGE_MANIFEST_FILENAME, parse_package from launch.action import Action -from launch.actions import IncludeLaunchDescription +from launch.actions import SetEnvironmentVariable from launch.frontend import Entity, expose_action, Parser from launch.launch_context import LaunchContext -from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.some_substitutions_type import SomeSubstitutionsType -from launch.substitutions import PathJoinSubstitution -from launch_ros.substitutions import FindPackageShare +from launch.substitutions import TextSubstitution +from launch.utilities.type_utils import normalize_typed_substitution, perform_typed_substitution +from launch_ros.actions import ComposableNodeContainer, LoadComposableNodes, Node +from launch_ros.descriptions import ComposableNode +from ros2pkg.api import get_package_names + + +""" +Search for model, plugin and media paths exported by packages. + +e.g. + + + +${prefix} is replaced by package's share directory in install. + +Thus the required directory needs to be installed from CMakeLists.txt +e.g. install(DIRECTORY models + DESTINATION share/${PROJECT_NAME}) +""" + + +class GazeboRosPaths: + + @staticmethod + def get_paths(): + gazebo_model_path = [] + gazebo_plugin_path = [] + gazebo_media_path = [] + + for package_name in get_package_names(): + package_share_path = get_package_share_directory(package_name) + package_file_path = os.path.join(package_share_path, PACKAGE_MANIFEST_FILENAME) + if os.path.isfile(package_file_path): + try: + package = parse_package(package_file_path) + except InvalidPackage: + continue + for export in package.exports: + if export.tagname == 'gazebo_ros': + if 'gazebo_model_path' in export.attributes: + xml_path = export.attributes['gazebo_model_path'] + xml_path = xml_path.replace('${prefix}', package_share_path) + gazebo_model_path.append(xml_path) + if 'plugin_path' in export.attributes: + xml_path = export.attributes['plugin_path'] + xml_path = xml_path.replace('${prefix}', package_share_path) + gazebo_plugin_path.append(xml_path) + if 'gazebo_media_path' in export.attributes: + xml_path = export.attributes['gazebo_media_path'] + xml_path = xml_path.replace('${prefix}', package_share_path) + gazebo_media_path.append(xml_path) + + gazebo_model_path = os.pathsep.join(gazebo_model_path + gazebo_media_path) + gazebo_plugin_path = os.pathsep.join(gazebo_plugin_path) + + return gazebo_model_path, gazebo_plugin_path @expose_action('gz_server') @@ -34,11 +90,11 @@ class GzServer(Action): def __init__( self, *, - world_sdf_file: Optional[SomeSubstitutionsType] = '', - world_sdf_string: Optional[SomeSubstitutionsType] = '', - container_name: Optional[SomeSubstitutionsType] = 'ros_gz_container', - create_own_container: Optional[SomeSubstitutionsType] = 'False', - use_composition: Optional[SomeSubstitutionsType] = 'False', + world_sdf_file: SomeSubstitutionsType = '', + world_sdf_string: SomeSubstitutionsType = '', + container_name: SomeSubstitutionsType = 'ros_gz_container', + create_own_container: Union[bool, SomeSubstitutionsType] = False, + use_composition: Union[bool, SomeSubstitutionsType] = False, **kwargs ) -> None: """ @@ -57,13 +113,29 @@ def __init__( self.__world_sdf_file = world_sdf_file self.__world_sdf_string = world_sdf_string self.__container_name = container_name - self.__create_own_container = create_own_container - self.__use_composition = use_composition + + # This is here to allow using strings or booleans as values for boolean variables when + # the Action is used from Python See the RosGzBridge.__init__ function for more details. + if isinstance(create_own_container, str): + self.__create_own_container = normalize_typed_substitution( + TextSubstitution(text=create_own_container), bool + ) + else: + self.__create_own_container = normalize_typed_substitution( + create_own_container, bool + ) + + if isinstance(use_composition, str): + self.__use_composition = normalize_typed_substitution( + TextSubstitution(text=use_composition), bool + ) + else: + self.__use_composition = normalize_typed_substitution(use_composition, bool) @classmethod def parse(cls, entity: Entity, parser: Parser): """Parse gz_server.""" - _, kwargs = super().parse(entity, parser) + kwargs: Dict = super().parse(entity, parser)[1] world_sdf_file = entity.get_attr( 'world_sdf_file', data_type=str, @@ -110,15 +182,73 @@ def parse(cls, entity: Entity, parser: Parser): def execute(self, context: LaunchContext) -> Optional[List[Action]]: """Execute the action.""" - gz_server_description = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [PathJoinSubstitution([FindPackageShare('ros_gz_sim'), - 'launch', - 'gz_server.launch.py'])]), - launch_arguments=[('world_sdf_file', self.__world_sdf_file), - ('world_sdf_string', self.__world_sdf_string), - ('container_name', self.__container_name), - ('create_own_container', self.__create_own_container), - ('use_composition', self.__use_composition), ]) - - return [gz_server_description] + launch_descriptions: List[Action] = [] + + model_paths, plugin_paths = GazeboRosPaths.get_paths() + launch_descriptions.append(SetEnvironmentVariable( + 'GZ_SIM_SYSTEM_PLUGIN_PATH', + os.pathsep.join([ + os.environ.get('GZ_SIM_SYSTEM_PLUGIN_PATH', default=''), + os.environ.get('LD_LIBRARY_PATH', default=''), + plugin_paths, + ]))) + launch_descriptions.append(SetEnvironmentVariable( + 'GZ_SIM_RESOURCE_PATH', + os.pathsep.join([ + os.environ.get('GZ_SIM_RESOURCE_PATH', default=''), + model_paths, + ]))) + + use_composition_eval = perform_typed_substitution( + context, self.__use_composition, bool + ) + create_own_container_eval = perform_typed_substitution( + context, self.__create_own_container, bool + ) + if not use_composition_eval: + # Standard node configuration + launch_descriptions.append(Node( + package='ros_gz_sim', + executable='gzserver', + output='screen', + parameters=[{'world_sdf_file': self.__world_sdf_file, + 'world_sdf_string': self.__world_sdf_string}], + )) + + # Composable node with container configuration + if use_composition_eval and create_own_container_eval: + launch_descriptions.append(ComposableNodeContainer( + name=self.__container_name, + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + ComposableNode( + package='ros_gz_sim', + plugin='ros_gz_sim::GzServer', + name='gz_server', + parameters=[{'world_sdf_file': self.__world_sdf_file, + 'world_sdf_string': self.__world_sdf_string}], + extra_arguments=[{'use_intra_process_comms': True}], + ), + ], + output='screen', + )) + + # Composable node without container configuration + if use_composition_eval and not create_own_container_eval: + launch_descriptions.append(LoadComposableNodes( + target_container=self.__container_name, + composable_node_descriptions=[ + ComposableNode( + package='ros_gz_sim', + plugin='ros_gz_sim::GzServer', + name='gz_server', + parameters=[{'world_sdf_file': self.__world_sdf_file, + 'world_sdf_string': self.__world_sdf_string}], + extra_arguments=[{'use_intra_process_comms': True}], + ), + ], + )) + + return launch_descriptions diff --git a/ros_gz_sim/src/remove.cpp b/ros_gz_sim/src/remove.cpp new file mode 100644 index 00000000..43df1758 --- /dev/null +++ b/ros_gz_sim/src/remove.cpp @@ -0,0 +1,110 @@ +// Copyright 2024 Open Source Robotics Foundation, Inc. +// +// 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. + +#include +#include +#include + +#include +#include +#include + +#include +#include + + +constexpr unsigned int timeout{5000}; +std::optional retrieve_world_name(const rclcpp::Node::SharedPtr & ros2_node) +{ + gz::transport::Node node; + bool executed{}; + bool result{}; + const std::string service{"/gazebo/worlds"}; + gz::msgs::StringMsg_V worlds_msg; + + // This loop is here to allow the server time to download resources. + while (rclcpp::ok() && !executed) { + RCLCPP_INFO(ros2_node->get_logger(), "Requesting list of world names."); + executed = node.Request(service, timeout, worlds_msg, result); + } + + if (!executed) { + RCLCPP_INFO(ros2_node->get_logger(), "Timed out when getting world names."); + return std::nullopt; + } + + if (!result || worlds_msg.data().empty()) { + RCLCPP_INFO(ros2_node->get_logger(), "Failed to get world names."); + return std::nullopt; + } + + return worlds_msg.data(0); +} + +int remove_entity( + const std::string & entity_name, + const std::string & world_name, + const rclcpp::Node::SharedPtr & ros2_node) +{ + const std::string service{"/world/" + world_name + "/remove"}; + gz::msgs::Entity entity_remove_request; + entity_remove_request.set_name(entity_name); + entity_remove_request.set_type(gz::msgs::Entity_Type_MODEL); + gz::transport::Node node; + gz::msgs::Boolean response; + bool result; + + while(rclcpp::ok() && !node.Request(service, entity_remove_request, timeout, response, result)) { + RCLCPP_WARN( + ros2_node->get_logger(), "Waiting for service [%s] to become available ...", + service.c_str()); + } + if (result && response.data()) { + RCLCPP_INFO(ros2_node->get_logger(), "Entity removal successful."); + return 0; + } else { + RCLCPP_ERROR( + ros2_node->get_logger(), "Entity removal failed.\n %s", + entity_remove_request.DebugString().c_str()); + return 1; + } + RCLCPP_INFO(ros2_node->get_logger(), "Entity removal was interrupted."); + return 1; +} + +int main(int _argc, char ** _argv) +{ + rclcpp::init(_argc, _argv); + auto ros2_node = rclcpp::Node::make_shared("ros_gz_sim_remove_entity"); + ros2_node->declare_parameter("world", ""); + ros2_node->declare_parameter("entity_name", ""); + + std::string world_name = ros2_node->get_parameter("world").as_string(); + if (world_name.empty()) { + world_name = retrieve_world_name(ros2_node).value_or(""); + if (world_name.empty()) { + return -1; + } + } + + const std::string entity_name = + ros2_node->get_parameter("entity_name").as_string(); + if (entity_name.empty()) { + RCLCPP_INFO(ros2_node->get_logger(), + "Entity to remove name is not provided, entity will not be removed."); + return -1; + } + + return remove_entity(entity_name, world_name, ros2_node); +} diff --git a/ros_gz_sim/test/test_remove.cpp b/ros_gz_sim/test/test_remove.cpp new file mode 100644 index 00000000..74a8f2ce --- /dev/null +++ b/ros_gz_sim/test/test_remove.cpp @@ -0,0 +1,56 @@ +// Copyright 2024 Open Source Robotics Foundation, Inc. +// +// 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. + +#include +#include + +#include +#include +#include +#include + +#include + +// Simple application that provides a `/remove` service and prints out the +// request's entity name. This works in conjuction with +// test_remove_node.launch.py +int main() +{ + std::mutex m; + std::condition_variable cv; + bool test_complete = false; + + gz::transport::Node node; + auto cb = std::function( + [&]( + const gz::msgs::Entity & _req, + gz::msgs::Boolean & _res) -> bool { + std::cout << _req.name() << std::endl; + _res.set_data(true); + + { + std::lock_guard lk(m); + test_complete = true; + } + cv.notify_one(); + return true; + }); + + node.Advertise("/world/default/remove", cb); + // wait until we receive a message. + std::unique_lock lk(m); + cv.wait(lk, [&] {return test_complete;}); + // Sleep so that the service response can be sent before exiting. + std::this_thread::sleep_for(std::chrono::seconds(1)); +} diff --git a/ros_gz_sim/test/test_remove_node.launch.py b/ros_gz_sim/test/test_remove_node.launch.py new file mode 100644 index 00000000..ebca2429 --- /dev/null +++ b/ros_gz_sim/test/test_remove_node.launch.py @@ -0,0 +1,49 @@ +# Copyright 2024 Open Source Robotics Foundation, Inc. +# +# 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. + +import unittest + +from launch import LaunchDescription + +from launch_ros.actions import Node + +import launch_testing + + +def generate_test_description(): + entity_name = 'my_robot' + remove = Node(package='ros_gz_sim', + executable='remove', + parameters=[{'world': 'default', 'entity_name': entity_name}], + output='screen') + test_remove = Node(package='ros_gz_sim', executable='test_remove', output='screen') + return LaunchDescription([ + remove, + test_remove, + launch_testing.util.KeepAliveProc(), + launch_testing.actions.ReadyToTest(), + ]), locals() + + +class WaitForTests(unittest.TestCase): + + def test_termination(self, test_remove, proc_info): + proc_info.assertWaitForShutdown(process=test_remove, timeout=200) + + +@launch_testing.post_shutdown_test() +class RemoveTest(unittest.TestCase): + + def test_output(self, entity_name, test_remove, proc_output): + launch_testing.asserts.assertInStdout(proc_output, entity_name, test_remove) diff --git a/ros_gz_sim_demos/CHANGELOG.rst b/ros_gz_sim_demos/CHANGELOG.rst index 7ee59f61..89dc117c 100644 --- a/ros_gz_sim_demos/CHANGELOG.rst +++ b/ros_gz_sim_demos/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package ros1_gz_sim_demos ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +2.1.2 (2024-10-31) +------------------ + 2.1.1 (2024-10-14) ------------------ diff --git a/ros_gz_sim_demos/CMakeLists.txt b/ros_gz_sim_demos/CMakeLists.txt index 8e0f580a..9b69104b 100644 --- a/ros_gz_sim_demos/CMakeLists.txt +++ b/ros_gz_sim_demos/CMakeLists.txt @@ -9,6 +9,12 @@ if(BUILD_TESTING) ament_lint_auto_find_test_dependencies() endif() +install( + DIRECTORY + config/ + DESTINATION share/${PROJECT_NAME}/config +) + install( DIRECTORY launch/ diff --git a/ros_gz_sim_demos/README.md b/ros_gz_sim_demos/README.md index c404a4dc..e3b64601 100644 --- a/ros_gz_sim_demos/README.md +++ b/ros_gz_sim_demos/README.md @@ -14,7 +14,7 @@ There's a convenient launch file, try for example: Publishes fluid pressure readings. - ros2 launch ros_gz_sim_demos air_pressure.launch.py + ros2 launch ros_gz_sim_demos air_pressure.launch.xml This demo also shows the use of custom QoS parameters. The sensor data is published as as "best-effort", so trying to subscribe to "reliable" data won't @@ -28,6 +28,18 @@ And ![](images/air_pressure_demo.png) +## Battery + +Get the current state of a battery. + + ros2 launch ros_gz_sim_demos battery.launch.py + +Then send a command so the vehicle moves and drains the battery. + + ros2 topic pub /model/vehicle_blue/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 5.0}, angular: {z: 0.5}}" + +![](images/battery_demo.png) + ## Camera Publishes RGB camera image and info. @@ -116,7 +128,7 @@ Using Gazebo Sim plugin: Publishes IMU readings. - ros2 launch ros_gz_sim_demos imu.launch.py + ros2 launch ros_gz_sim_demos imu.launch.xml ![](images/imu_demo.png) @@ -126,7 +138,7 @@ Publishes IMU readings. Publishes magnetic field readings. - ros2 launch ros_gz_sim_demos magnetometer.launch.py + ros2 launch ros_gz_sim_demos magnetometer.launch.xml ![](images/magnetometer_demo.png) @@ -134,7 +146,12 @@ Publishes magnetic field readings. Publishes satellite navigation readings, only available in Fortress on. - ros2 launch ros_gz_sim_demos navsat.launch.py +GNSS information can be obtained as: + + # sensor_msgs/msg/NavSatFix + ros2 launch ros_gz_sim_demos navsat.launch.xml + # gps_msgs/msg/GPSFix + ros2 launch ros_gz_sim_demos navsat_gpsfix.launch.xml ![](images/navsat_demo.png) @@ -164,18 +181,6 @@ Using Gazebo Sim plugin: ![](images/rgbd_camera_demo.png) -## Battery - -Get the current state of a battery. - - ros2 launch ros_gz_sim_demos battery.launch.py - -Then send a command so the vehicle moves and drains the battery - - ros2 topic pub /model/vehicle_blue/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 5.0}, angular: {z: 0.5}}" - -![](images/battery_demo.png) - ## Robot description publisher Leverage the robot description publisher to spawn a new urdf model in gazebo and @@ -203,6 +208,6 @@ and transforms of a robot in rviz. To try the demo launch: - ros2 launch ros_gz_sim_demos tf_bridge.launch.py + ros2 launch ros_gz_sim_demos tf_bridge.launch.xml ![](images/tf_bridge.gif) diff --git a/ros_gz_sim_demos/config/air_pressure.yaml b/ros_gz_sim_demos/config/air_pressure.yaml new file mode 100644 index 00000000..36ad5f67 --- /dev/null +++ b/ros_gz_sim_demos/config/air_pressure.yaml @@ -0,0 +1,6 @@ +# Air pressure bridge configuration. +- topic_name: "air_pressure" + ros_type_name: "sensor_msgs/msg/FluidPressure" + gz_type_name: "gz.msgs.FluidPressure" + lazy: true + direction: GZ_TO_ROS diff --git a/ros_gz_sim_demos/config/battery.yaml b/ros_gz_sim_demos/config/battery.yaml new file mode 100644 index 00000000..9f6494bb --- /dev/null +++ b/ros_gz_sim_demos/config/battery.yaml @@ -0,0 +1,12 @@ +# Battery configuration. +- topic_name: "/model/vehicle_blue/cmd_vel" + ros_type_name: "geometry_msgs/msg/Twist" + gz_type_name: "gz.msgs.Twist" + lazy: true + direction: ROS_TO_GZ + +- topic_name: "/model/vehicle_blue/battery/linear_battery/state" + ros_type_name: "sensor_msgs/msg/BatteryState" + gz_type_name: "gz.msgs.BatteryState" + lazy: true + direction: GZ_TO_ROS diff --git a/ros_gz_sim_demos/config/imu.yaml b/ros_gz_sim_demos/config/imu.yaml new file mode 100644 index 00000000..9a3ddbcd --- /dev/null +++ b/ros_gz_sim_demos/config/imu.yaml @@ -0,0 +1,6 @@ +# IMU configuration. +- topic_name: "/imu" + ros_type_name: "sensor_msgs/msg/Imu" + gz_type_name: "gz.msgs.IMU" + lazy: true + direction: GZ_TO_ROS diff --git a/ros_gz_sim_demos/config/magnetometer.yaml b/ros_gz_sim_demos/config/magnetometer.yaml new file mode 100644 index 00000000..e5d38d2e --- /dev/null +++ b/ros_gz_sim_demos/config/magnetometer.yaml @@ -0,0 +1,6 @@ +# Magnetometer configuration. +- topic_name: "/magnetometer" + ros_type_name: "sensor_msgs/msg/MagneticField" + gz_type_name: "gz.msgs.Magnetometer" + lazy: true + direction: GZ_TO_ROS diff --git a/ros_gz_sim_demos/config/navsat.yaml b/ros_gz_sim_demos/config/navsat.yaml new file mode 100644 index 00000000..0a68ab80 --- /dev/null +++ b/ros_gz_sim_demos/config/navsat.yaml @@ -0,0 +1,6 @@ +# Navsat configuration. +- topic_name: "/navsat" + ros_type_name: "sensor_msgs/msg/NavSatFix" + gz_type_name: "gz.msgs.NavSat" + lazy: true + direction: GZ_TO_ROS diff --git a/ros_gz_sim_demos/config/navsat_gpsfix.yaml b/ros_gz_sim_demos/config/navsat_gpsfix.yaml new file mode 100644 index 00000000..07784eee --- /dev/null +++ b/ros_gz_sim_demos/config/navsat_gpsfix.yaml @@ -0,0 +1,6 @@ +# Navsat_gpsfix configuration. +- topic_name: "/navsat" + ros_type_name: "gps_msgs/msg/GPSFix" + gz_type_name: "gz.msgs.NavSat" + lazy: true + direction: GZ_TO_ROS diff --git a/ros_gz_sim_demos/config/tf_bridge.yaml b/ros_gz_sim_demos/config/tf_bridge.yaml new file mode 100644 index 00000000..9debbf11 --- /dev/null +++ b/ros_gz_sim_demos/config/tf_bridge.yaml @@ -0,0 +1,14 @@ +# tf_bridge configuration. +- ros_topic_name: "/joint_states" + gz_topic_name: "/world/default/model/double_pendulum_with_base0/joint_state" + ros_type_name: "sensor_msgs/msg/JointState" + gz_type_name: "gz.msgs.Model" + lazy: true + direction: GZ_TO_ROS + +- ros_topic_name: "/tf" + gz_topic_name: "/model/double_pendulum_with_base0/pose" + ros_type_name: "tf2_msgs/msg/TFMessage" + gz_type_name: "gz.msgs.Pose_V" + lazy: true + direction: GZ_TO_ROS diff --git a/ros_gz_sim_demos/launch/air_pressure.launch.py b/ros_gz_sim_demos/launch/air_pressure.launch.py deleted file mode 100644 index 2a422b34..00000000 --- a/ros_gz_sim_demos/launch/air_pressure.launch.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2019 Open Source Robotics Foundation, Inc. -# -# 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. - -"""Launch Gazebo Sim with command line arguments.""" - -import os - -from ament_index_python.packages import get_package_share_directory - -from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument -from launch.actions import IncludeLaunchDescription -from launch.conditions import IfCondition -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration - -from launch_ros.actions import Node - - -def generate_launch_description(): - - pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') - - # Bridge - bridge = Node( - package='ros_gz_bridge', - executable='parameter_bridge', - arguments=['/air_pressure@sensor_msgs/msg/FluidPressure@gz.msgs.FluidPressure'], - parameters=[{'qos_overrides./air_pressure.publisher.reliability': 'best_effort'}], - output='screen' - ) - - gz_sim = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')), - launch_arguments={'gz_args': '-r sensors.sdf'}.items(), - ) - - # RQt - rqt = Node( - package='rqt_topic', - executable='rqt_topic', - arguments=['-t'], - condition=IfCondition(LaunchConfiguration('rqt')) - ) - return LaunchDescription([ - gz_sim, - DeclareLaunchArgument('rqt', default_value='true', - description='Open RQt.'), - bridge, - rqt - ]) diff --git a/ros_gz_sim_demos/launch/air_pressure.launch.xml b/ros_gz_sim_demos/launch/air_pressure.launch.xml new file mode 100644 index 00000000..9fd33397 --- /dev/null +++ b/ros_gz_sim_demos/launch/air_pressure.launch.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/ros_gz_sim_demos/launch/battery.launch.py b/ros_gz_sim_demos/launch/battery.launch.py index 8abe8eff..a0da9fdf 100644 --- a/ros_gz_sim_demos/launch/battery.launch.py +++ b/ros_gz_sim_demos/launch/battery.launch.py @@ -15,20 +15,20 @@ import os from ament_index_python.packages import get_package_share_directory - from launch import LaunchDescription from launch.actions import DeclareLaunchArgument from launch.actions import IncludeLaunchDescription from launch.conditions import IfCondition from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration - from launch_ros.actions import Node +from ros_gz_bridge.actions import RosGzBridge def generate_launch_description(): pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') + pkg_ros_gz_sim_demos = get_package_share_directory('ros_gz_sim_demos') # RQt rqt = Node( @@ -40,6 +40,7 @@ def generate_launch_description(): condition=IfCondition(LaunchConfiguration('rqt')) ) + # Gazebo gz_sim = IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')), @@ -49,21 +50,15 @@ def generate_launch_description(): ) # Bridge - bridge = Node( - package='ros_gz_bridge', - executable='parameter_bridge', - arguments=[ - '/model/vehicle_blue/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist', - '/model/vehicle_blue/battery/linear_battery/state@sensor_msgs/msg/BatteryState@' - 'gz.msgs.BatteryState' - ], - output='screen' + ros_gz_bridge = RosGzBridge( + bridge_name='ros_gz_bridge', + config_file=os.path.join(pkg_ros_gz_sim_demos, 'config', 'battery.yaml'), ) return LaunchDescription([ gz_sim, DeclareLaunchArgument('rqt', default_value='true', description='Open RQt.'), - bridge, + ros_gz_bridge, rqt ]) diff --git a/ros_gz_sim_demos/launch/imu.launch.py b/ros_gz_sim_demos/launch/imu.launch.py deleted file mode 100644 index cc1bf9e9..00000000 --- a/ros_gz_sim_demos/launch/imu.launch.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 2019 Open Source Robotics Foundation, Inc. -# -# 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. - -import os - -from ament_index_python.packages import get_package_share_directory - -from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument -from launch.actions import IncludeLaunchDescription -from launch.conditions import IfCondition -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration - -from launch_ros.actions import Node - - -def generate_launch_description(): - - pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') - - gz_sim = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')), - launch_arguments={ - 'gz_args': '-r sensors.sdf' - }.items(), - ) - - # RQt - rqt = Node( - package='rqt_topic', - executable='rqt_topic', - arguments=['-t'], - condition=IfCondition(LaunchConfiguration('rqt')) - ) - - # RViz - # FIXME: Add once there's an IMU display for RViz2 - # pkg_ros_gz_sim_demos = get_package_share_directory('ros_gz_sim_demos') - # rviz = Node( - # package='rviz2', - # executable='rviz2', - # # arguments=['-d', os.path.join(pkg_ros_gz_sim_demos, 'rviz', 'imu.rviz')], - # condition=IfCondition(LaunchConfiguration('rviz')) - # ) - - # Bridge - bridge = Node( - package='ros_gz_bridge', - executable='parameter_bridge', - arguments=['/imu@sensor_msgs/msg/Imu@gz.msgs.IMU'], - output='screen' - ) - - return LaunchDescription([ - gz_sim, - DeclareLaunchArgument( - 'rqt', default_value='true', description='Open RQt.' - ), - DeclareLaunchArgument( - 'rviz', default_value='true', description='Open RViz.' - ), - bridge, - rqt - # rviz - ]) diff --git a/ros_gz_sim_demos/launch/imu.launch.xml b/ros_gz_sim_demos/launch/imu.launch.xml new file mode 100644 index 00000000..d7ef727c --- /dev/null +++ b/ros_gz_sim_demos/launch/imu.launch.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/ros_gz_sim_demos/launch/magnetometer.launch.py b/ros_gz_sim_demos/launch/magnetometer.launch.py deleted file mode 100644 index e35b8487..00000000 --- a/ros_gz_sim_demos/launch/magnetometer.launch.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2019 Open Source Robotics Foundation, Inc. -# -# 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. - -import os - -from ament_index_python.packages import get_package_share_directory - -from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument -from launch.actions import IncludeLaunchDescription -from launch.conditions import IfCondition -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration - -from launch_ros.actions import Node - - -def generate_launch_description(): - - pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') - - gz_sim = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')), - launch_arguments={ - 'gz_args': '-r sensors.sdf' - }.items(), - ) - - # RQt - rqt = Node( - package='rqt_topic', - executable='rqt_topic', - arguments=['-t'], - condition=IfCondition(LaunchConfiguration('rqt')) - ) - - # Bridge - bridge = Node( - package='ros_gz_bridge', - executable='parameter_bridge', - arguments=['/magnetometer@sensor_msgs/msg/MagneticField@gz.msgs.Magnetometer'], - output='screen' - ) - - return LaunchDescription([ - gz_sim, - DeclareLaunchArgument('rqt', default_value='true', - description='Open RQt.'), - bridge, - rqt - ]) diff --git a/ros_gz_sim_demos/launch/magnetometer.launch.xml b/ros_gz_sim_demos/launch/magnetometer.launch.xml new file mode 100644 index 00000000..10639b43 --- /dev/null +++ b/ros_gz_sim_demos/launch/magnetometer.launch.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/ros_gz_sim_demos/launch/navsat.launch.py b/ros_gz_sim_demos/launch/navsat.launch.py deleted file mode 100644 index f2172f93..00000000 --- a/ros_gz_sim_demos/launch/navsat.launch.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2019 Open Source Robotics Foundation, Inc. -# -# 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. - -import os - -from ament_index_python.packages import get_package_share_directory - -from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument -from launch.actions import IncludeLaunchDescription -from launch.conditions import IfCondition -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration - -from launch_ros.actions import Node - - -def generate_launch_description(): - - pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') - - gz_sim = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')), - launch_arguments={ - 'gz_args': '-v 4 -r spherical_coordinates.sdf' - }.items(), - ) - - # RQt - rqt = Node( - package='rqt_topic', - executable='rqt_topic', - arguments=['-t'], - condition=IfCondition(LaunchConfiguration('rqt')) - ) - - # Bridge - bridge = Node( - package='ros_gz_bridge', - executable='parameter_bridge', - arguments=['/navsat@sensor_msgs/msg/NavSatFix@gz.msgs.NavSat'], - output='screen' - ) - - return LaunchDescription([ - gz_sim, - DeclareLaunchArgument('rqt', default_value='true', - description='Open RQt.'), - bridge, - rqt - ]) diff --git a/ros_gz_sim_demos/launch/navsat.launch.xml b/ros_gz_sim_demos/launch/navsat.launch.xml new file mode 100644 index 00000000..97ad0adc --- /dev/null +++ b/ros_gz_sim_demos/launch/navsat.launch.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/ros_gz_sim_demos/launch/navsat_gpsfix.launch.py b/ros_gz_sim_demos/launch/navsat_gpsfix.launch.py deleted file mode 100644 index 83adcdcc..00000000 --- a/ros_gz_sim_demos/launch/navsat_gpsfix.launch.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2022 Open Source Robotics Foundation, Inc. -# -# 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. - -import os - -from ament_index_python.packages import get_package_share_directory - -from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument -from launch.actions import IncludeLaunchDescription -from launch.conditions import IfCondition -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration - -from launch_ros.actions import Node - - -def generate_launch_description(): - - pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') - - gz_sim = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')), - launch_arguments={ - 'gz_args': '-v 4 -r spherical_coordinates.sdf' - }.items(), - ) - - # RQt - rqt = Node( - package='rqt_topic', - executable='rqt_topic', - arguments=['-t'], - condition=IfCondition(LaunchConfiguration('rqt')) - ) - - # Bridge - bridge = Node( - package='ros_gz_bridge', - executable='parameter_bridge', - arguments=['/navsat@gps_msgs/msg/GPSFix@gz.msgs.NavSat'], - output='screen' - ) - - return LaunchDescription([ - gz_sim, - DeclareLaunchArgument('rqt', default_value='true', - description='Open RQt.'), - bridge, - rqt - ]) diff --git a/ros_gz_sim_demos/launch/navsat_gpsfix.launch.xml b/ros_gz_sim_demos/launch/navsat_gpsfix.launch.xml new file mode 100644 index 00000000..10c8c8a4 --- /dev/null +++ b/ros_gz_sim_demos/launch/navsat_gpsfix.launch.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/ros_gz_sim_demos/launch/tf_bridge.launch.py b/ros_gz_sim_demos/launch/tf_bridge.launch.py deleted file mode 100644 index 42f76cdc..00000000 --- a/ros_gz_sim_demos/launch/tf_bridge.launch.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2022 Open Source Robotics Foundation, Inc. -# -# 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. - -import os - -from ament_index_python.packages import get_package_share_directory - -from launch import LaunchDescription -from launch.actions import ExecuteProcess -from launch_ros.actions import Node - - -def generate_launch_description(): - pkg_ros_gz_sim_demos = get_package_share_directory('ros_gz_sim_demos') - return LaunchDescription([ - # Launch gazebo - ExecuteProcess( - cmd=[ - 'gz', 'sim', '-r', - os.path.join( - pkg_ros_gz_sim_demos, - 'models', - 'double_pendulum_model.sdf' - ) - ] - ), - # Launch a bridge to forward tf and joint states to ros2 - Node( - package='ros_gz_bridge', - executable='parameter_bridge', - arguments=[ - '/world/default/model/double_pendulum_with_base0/joint_state@' - 'sensor_msgs/msg/JointState[gz.msgs.Model', - '/model/double_pendulum_with_base0/pose@' - 'tf2_msgs/msg/TFMessage[gz.msgs.Pose_V' - ], - remappings=[ - ('/model/double_pendulum_with_base0/pose', '/tf'), - ('/world/default/model/double_pendulum_with_base0/joint_state', '/joint_states') - ] - ), - # Launch rviz - Node( - package='rviz2', - executable='rviz2', - arguments=['-d', os.path.join(pkg_ros_gz_sim_demos, 'rviz', 'tf_bridge.rviz')] - ) - ]) diff --git a/ros_gz_sim_demos/launch/tf_bridge.launch.xml b/ros_gz_sim_demos/launch/tf_bridge.launch.xml new file mode 100644 index 00000000..321a3d8c --- /dev/null +++ b/ros_gz_sim_demos/launch/tf_bridge.launch.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/ros_gz_sim_demos/package.xml b/ros_gz_sim_demos/package.xml index 13242084..414af4fe 100644 --- a/ros_gz_sim_demos/package.xml +++ b/ros_gz_sim_demos/package.xml @@ -1,6 +1,6 @@ ros_gz_sim_demos - 2.1.1 + 2.1.2 Demos using Gazebo Sim simulation with ROS. Apache 2.0 Aditya Pande @@ -22,6 +22,7 @@ rqt_image_view rqt_plot rqt_topic + rviz_imu_plugin rviz2 sdformat_urdf xacro diff --git a/ros_gz_sim_demos/rviz/imu.rviz b/ros_gz_sim_demos/rviz/imu.rviz index 0169390a..2d825af4 100644 --- a/ros_gz_sim_demos/rviz/imu.rviz +++ b/ros_gz_sim_demos/rviz/imu.rviz @@ -1,43 +1,41 @@ Panels: - - Class: rviz/Displays + - Class: rviz_common/Displays Help Height: 78 Name: Displays Property Tree Widget: Expanded: - /Global Options1 + - /Status1 + - /Grid1 - /Imu1 - - /Imu1/Status1 + - /Imu1/Axes properties1 + - /Imu1/Acceleration properties1 Splitter Ratio: 0.5 - Tree Height: 352 - - Class: rviz/Selection + Tree Height: 555 + - Class: rviz_common/Selection Name: Selection - - Class: rviz/Tool Properties + - Class: rviz_common/Tool Properties Expanded: - - /2D Pose Estimate1 - - /2D Nav Goal1 + - /2D Goal Pose1 - /Publish Point1 Name: Tool Properties Splitter Ratio: 0.5886790156364441 - - Class: rviz/Views + - Class: rviz_common/Views Expanded: - /Current View1 Name: Views Splitter Ratio: 0.5 - - Class: rviz/Time + - Class: rviz_common/Time Experimental: false Name: Time SyncMode: 0 SyncSource: "" -Preferences: - PromptSaveOnExit: true -Toolbars: - toolButtonStyle: 2 Visualization Manager: Class: "" Displays: - Alpha: 0.5 Cell Size: 1 - Class: rviz/Grid + Class: rviz_default_plugins/Grid Color: 160; 160; 164 Enabled: true Line Style: @@ -53,53 +51,90 @@ Visualization Manager: Plane Cell Count: 10 Reference Frame: Value: true - - Alpha: 1 - Class: rviz_plugin_tutorials/Imu - Color: 204; 51; 204 + - Acceleration properties: + Acc. vector alpha: 1 + Acc. vector color: 255; 0; 0 + Acc. vector scale: 1 + Derotate acceleration: true + Enable acceleration: true + Axes properties: + Axes scale: 1 + Enable axes: true + Box properties: + Box alpha: 1 + Box color: 255; 0; 0 + Enable box: false + x_scale: 1 + y_scale: 1 + z_scale: 1 + Class: rviz_imu_plugin/Imu Enabled: true - History Length: 1 Name: Imu - Topic: /imu - Unreliable: false + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /imu Value: true + fixed_frame_orientation: true Enabled: true Global Options: Background Color: 48; 48; 48 - Default Light: true - Fixed Frame: sensors/sensors_box/link/imu + Fixed Frame: sensors_box/link/imu Frame Rate: 30 Name: root Tools: - - Class: rviz/Interact + - Class: rviz_default_plugins/Interact Hide Inactive Objects: true - - Class: rviz/MoveCamera - - Class: rviz/Select - - Class: rviz/FocusCamera - - Class: rviz/Measure - - Class: rviz/SetInitialPose - Theta std deviation: 0.2617993950843811 - Topic: /initialpose - X std deviation: 0.5 - Y std deviation: 0.5 - - Class: rviz/SetGoal - Topic: /move_base_simple/goal - - Class: rviz/PublishPoint + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Covariance x: 0.25 + Covariance y: 0.25 + Covariance yaw: 0.06853891909122467 + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint Single click: true - Topic: /clicked_point + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF Value: true Views: Current: - Class: rviz/Orbit - Distance: 33.22251510620117 + Class: rviz_default_plugins/Orbit + Distance: 29.85377311706543 Enable Stereo Rendering: Stereo Eye Separation: 0.05999999865889549 Stereo Focal Distance: 1 Swap Stereo Eyes: false Value: false Focal Point: - X: -1.7206333875656128 - Y: -0.9246708154678345 - Z: 1.870512843132019 + X: 0 + Y: 0 + Z: 0 Focal Shape Fixed Size: true Focal Shape Size: 0.05000000074505806 Invert Z Axis: false @@ -113,10 +148,10 @@ Visualization Manager: Window Geometry: Displays: collapsed: false - Height: 575 + Height: 846 Hide Left Dock: false Hide Right Dock: false - QMainWindow State: 000000ff00000000fd000000040000000000000191000001e9fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b000001e9000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000002b4fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b000002b4000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000003d70000003efc0100000002fb0000000800540069006d00650000000000000003d70000024400fffffffb0000000800540069006d00650100000000000004500000000000000000000001a2000001e900000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + QMainWindow State: 000000ff00000000fd000000040000000000000156000002b4fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b000002b4000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000002b4fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003b000002b4000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004b00000003efc0100000002fb0000000800540069006d00650100000000000004b00000025300fffffffb0000000800540069006d006501000000000000045000000000000000000000023f000002b400000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 Selection: collapsed: false Time: @@ -125,6 +160,6 @@ Window Geometry: collapsed: false Views: collapsed: false - Width: 825 - X: 751 - Y: 92 + Width: 1200 + X: 2219 + Y: 69 diff --git a/test_ros_gz_bridge/CHANGELOG.rst b/test_ros_gz_bridge/CHANGELOG.rst index bebf4abc..b4f1e3b6 100644 --- a/test_ros_gz_bridge/CHANGELOG.rst +++ b/test_ros_gz_bridge/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package test_ros_gz_bridge ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +2.1.2 (2024-10-31) +------------------ + 2.1.1 (2024-10-14) ------------------ diff --git a/test_ros_gz_bridge/package.xml b/test_ros_gz_bridge/package.xml index 4f200091..95da57f1 100644 --- a/test_ros_gz_bridge/package.xml +++ b/test_ros_gz_bridge/package.xml @@ -2,7 +2,7 @@ test_ros_gz_bridge - 2.1.1 + 2.1.2 Bridge communication between ROS and Gazebo Transport Aditya Pande Alejandro Hernandez