diff --git a/launch_gz/launch_gz/actions/ros_gz_bridge.py b/launch_gz/launch_gz/actions/ros_gz_bridge.py new file mode 100644 index 00000000..8cacc1e3 --- /dev/null +++ b/launch_gz/launch_gz/actions/ros_gz_bridge.py @@ -0,0 +1,139 @@ +# 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. + +"""Module for the ros_gz bridge action.""" + +from typing import List +from typing import Optional + +from launch.action import Action +from launch.actions import IncludeLaunchDescription +from launch.frontend import expose_action, Entity, 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 + +@expose_action('ros_gz_bridge') +class RosGzBridge(Action): + """Action that executes a ros_gz bridge ROS [composable] node.""" + + def __init__( + self, + *, + config_file: SomeSubstitutionsType, + container_name: SomeSubstitutionsType, + namespace: SomeSubstitutionsType, + use_composition: SomeSubstitutionsType, + use_respawn: SomeSubstitutionsType, + log_level: SomeSubstitutionsType, + **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: config_file YAML config file. + :param: container_name Name of container that nodes will load in if use composition. + :param: namespace Top-level namespace. + :param: use_composition Use composed bringup if True. + :param: use_respawn Whether to respawn if a node crashes. Applied when composition is disabled.. + :param: log_level Log level. + """ + + super().__init__(**kwargs) + self.__config_file = config_file + self.__container_name = container_name + self.__namespace = namespace + self.__use_composition = use_composition + self.__use_respawn = use_respawn + self.__log_level = log_level + + @classmethod + def parse(cls, entity: Entity, parser: Parser): + """Parse ros_gz_bridge.""" + _, kwargs = super().parse(entity, parser) + + config_file = entity.get_attr( + 'config_file', data_type=str, + optional=False) + + container_name = entity.get_attr( + 'container_name', data_type=str, + optional=True) + + namespace = entity.get_attr( + 'namespace', data_type=str, + optional=True) + + use_composition = entity.get_attr( + 'use_composition', data_type=str, + optional=True) + + use_respawn = entity.get_attr( + 'use_respawn', data_type=str, + optional=True) + + log_level = entity.get_attr( + 'log_level', data_type=str, + optional=True) + + if isinstance(config_file, str): + config_file = parser.parse_substitution(config_file) + kwargs['config_file'] = config_file + + if isinstance(container_name, str): + container_name = parser.parse_substitution(container_name) + kwargs['container_name'] = container_name + + if isinstance(namespace, str): + namespace = parser.parse_substitution(namespace) + kwargs['namespace'] = namespace + + if isinstance(use_composition, str): + use_composition = parser.parse_substitution(use_composition) + kwargs['use_composition'] = use_composition + + if isinstance(use_respawn, str): + use_respawn = parser.parse_substitution(use_respawn) + kwargs['use_respawn'] = use_respawn + + if isinstance(log_level, str): + log_level = parser.parse_substitution(log_level) + kwargs['log_level'] = log_level + + 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=[('config_file', self.__config_file), + ('container_name', self.__container_name), + ('namespace', self.__namespace), + ('use_composition', self.__use_composition), + ('use_respawn', self.__use_respawn), + ('log_level', self.__log_level), + ]) + + return [gzserver_description] diff --git a/ros_gz_bridge/launch/ros_gz_bridge.launch b/ros_gz_bridge/launch/ros_gz_bridge.launch new file mode 100644 index 00000000..367b4ba5 --- /dev/null +++ b/ros_gz_bridge/launch/ros_gz_bridge.launch @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/ros_gz_sim/CMakeLists.txt b/ros_gz_sim/CMakeLists.txt index 06ade215..e6495ed1 100644 --- a/ros_gz_sim/CMakeLists.txt +++ b/ros_gz_sim/CMakeLists.txt @@ -97,6 +97,7 @@ install(FILES "launch/gzserver.launch.py" "launch/gz_spawn_model.launch.py" "launch/ros_gz_sim.launch.py" + "launch/ros_gz_sim.launch.py" "launch/ros_gz_spawn_model.launch.py" DESTINATION share/${PROJECT_NAME}/launch ) diff --git a/ros_gz_sim/launch/gzserver.launch b/ros_gz_sim/launch/gzserver.launch index 93d19d22..a3275b68 100644 --- a/ros_gz_sim/launch/gzserver.launch +++ b/ros_gz_sim/launch/gzserver.launch @@ -6,7 +6,7 @@ + container_name="$(var container_name)" + use_composition="$(var use_composition)"> diff --git a/ros_gz_sim/launch/ros_gz_sim.launch b/ros_gz_sim/launch/ros_gz_sim.launch new file mode 100644 index 00000000..ff9d242a --- /dev/null +++ b/ros_gz_sim/launch/ros_gz_sim.launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + +