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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+