diff --git a/README.md b/README.md
index 5b7020c..2176172 100644
--- a/README.md
+++ b/README.md
@@ -34,10 +34,11 @@ inmoov_urdf/
│ │ ├── urdf/robot_description.urdf.xacro # links, joints, visuals, collisions, materials
│ │ └── meshes/dae/*.dae # 290 Collada meshes (visual + collision)
│ ├── ros2_control/
-│ │ ├── inmoov_ros2_control.xacro # hardware interfaces (real / sim / mock)
-│ │ └── inmoov_gz_ros2_control.xacro # gz_ros2_control plugin (sim only)
+│ │ └── inmoov_ros2_control.xacro # hardware interfaces (real / mock)
│ └── gazebo/
-│ └── inmoov_gazebo_physics.xacro # static / friction / self_collide (sim only)
+│ ├── inmoov_gazebo_physics.xacro # static / friction / self_collide (sim only)
+│ ├── gazebo.xacro # generated gz_ros2_control plugin + camera sensors (sim only)
+│ └── gazebo_bridge.yaml # generated ros_gz_bridge topic map (sim only)
├── worlds/default.sdf # minimal world used by gazebo.launch.py
├── scripts/ # inject_collisions.py, autocalibrate_joint_limits.py, scale_xacro_origins.py
├── test/ # pytest suite (xacro smoke, YAML, collisions, joint limits)
diff --git a/config/gazebo_bridge.yaml b/config/gazebo_bridge.yaml
deleted file mode 100644
index 6f6f708..0000000
--- a/config/gazebo_bridge.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
-- ros_topic_name: "/clock"
- gz_topic_name: "/world/default/clock"
- ros_type_name: "rosgraph_msgs/msg/Clock"
- gz_type_name: "gz.msgs.Clock"
- direction: GZ_TO_ROS
-
-- ros_topic_name: "/camera/gazebo/raw"
- gz_topic_name: "/world/default/camera"
- ros_type_name: "sensor_msgs/msg/Image"
- gz_type_name: "gz.msgs.Image"
- direction: GZ_TO_ROS
-
-- ros_topic_name: "/sensor_right_arm/mock_sensor_data"
- gz_topic_name: "/sensor_right_arm/mock_sensor_data"
- ros_type_name: "ros_gz_interfaces/msg/Float32Array"
- gz_type_name: "gz.msgs.Float_V"
- direction: GZ_TO_ROS
-
-- ros_topic_name: "/sensor_left_arm/mock_sensor_data"
- gz_topic_name: "/sensor_left_arm/mock_sensor_data"
- ros_type_name: "ros_gz_interfaces/msg/Float32Array"
- gz_type_name: "gz.msgs.Float_V"
- direction: GZ_TO_ROS
diff --git a/config/hardware/active.yaml b/config/hardware/active.yaml
index 9bab948..708200b 100644
--- a/config/hardware/active.yaml
+++ b/config/hardware/active.yaml
@@ -33,6 +33,48 @@ ignore_urdf_joints:
- i01.rightHand.thumb1_link_joint
- i01.rightHand.thumb3_link_joint
- i01.rightHand.wrist.001_link_joint
+
+cameras:
+ - id: 'ext_camera'
+ name: 'External USB Webcam'
+ topic: '/ext_camera/jpg'
+ compressed_topic: '/ext_camera/jpg/compressed'
+ message_type: 'sensor_msgs/msg/CompressedImage'
+ external: true
+ sim_gz_topic: '/world/default/camera'
+
+ - realsense-rgb:
+ name: 'Realsense RGB'
+ topic: '/realsense/realsense2_camera/color/image_raw'
+ compressed_topic: '/realsense/realsense2_camera/color/image_raw/compressed'
+ message_type: 'sensor_msgs/msg/CompressedImage'
+ external: false
+ link: 'i01.head.eyeLeft_link'
+
+ - realsense-aligned-depth:
+ name: 'Realsense Aligned Depth'
+ topic: '/realsense/realsense2_camera/aligned_depth_to_color/image_raw'
+ compressed_topic: '/realsense/realsense2_camera/aligned_depth_to_color/image_raw/compressed'
+ message_type: 'sensor_msgs/msg/CompressedImage'
+ external: false
+ link: 'i01.head.eyeLeft_link'
+
+ - left_eye_camera:
+ name: 'Left eye webcam'
+ topic: '/camera/internal/left_eye/jpg'
+ compressed_topic: '/camera/internal/left_eye/jpg/compressed'
+ message_type: 'sensor_msgs/msg/CompressedImage'
+ external: false
+ link: 'i01.head.eyeRight_link'
+
+ - right_eye_camera:
+ name: 'Right eye webcam'
+ topic: '/camera/internal/right_eye/jpg'
+ compressed_topic: '/camera/internal/right_eye/jpg/compressed'
+ message_type: 'sensor_msgs/msg/CompressedImage'
+ external: false
+ link: 'i01.head.eyeLeft_link'
+
firmware:
source_dir: micro_ros_raspberrypi_pico_sdk
build_dir: build
diff --git a/config/hardware/configs/default.yaml b/config/hardware/configs/default.yaml
index 9bab948..708200b 100644
--- a/config/hardware/configs/default.yaml
+++ b/config/hardware/configs/default.yaml
@@ -33,6 +33,48 @@ ignore_urdf_joints:
- i01.rightHand.thumb1_link_joint
- i01.rightHand.thumb3_link_joint
- i01.rightHand.wrist.001_link_joint
+
+cameras:
+ - id: 'ext_camera'
+ name: 'External USB Webcam'
+ topic: '/ext_camera/jpg'
+ compressed_topic: '/ext_camera/jpg/compressed'
+ message_type: 'sensor_msgs/msg/CompressedImage'
+ external: true
+ sim_gz_topic: '/world/default/camera'
+
+ - realsense-rgb:
+ name: 'Realsense RGB'
+ topic: '/realsense/realsense2_camera/color/image_raw'
+ compressed_topic: '/realsense/realsense2_camera/color/image_raw/compressed'
+ message_type: 'sensor_msgs/msg/CompressedImage'
+ external: false
+ link: 'i01.head.eyeLeft_link'
+
+ - realsense-aligned-depth:
+ name: 'Realsense Aligned Depth'
+ topic: '/realsense/realsense2_camera/aligned_depth_to_color/image_raw'
+ compressed_topic: '/realsense/realsense2_camera/aligned_depth_to_color/image_raw/compressed'
+ message_type: 'sensor_msgs/msg/CompressedImage'
+ external: false
+ link: 'i01.head.eyeLeft_link'
+
+ - left_eye_camera:
+ name: 'Left eye webcam'
+ topic: '/camera/internal/left_eye/jpg'
+ compressed_topic: '/camera/internal/left_eye/jpg/compressed'
+ message_type: 'sensor_msgs/msg/CompressedImage'
+ external: false
+ link: 'i01.head.eyeRight_link'
+
+ - right_eye_camera:
+ name: 'Right eye webcam'
+ topic: '/camera/internal/right_eye/jpg'
+ compressed_topic: '/camera/internal/right_eye/jpg/compressed'
+ message_type: 'sensor_msgs/msg/CompressedImage'
+ external: false
+ link: 'i01.head.eyeLeft_link'
+
firmware:
source_dir: micro_ros_raspberrypi_pico_sdk
build_dir: build
diff --git a/description/gazebo/gazebo.xacro b/description/gazebo/gazebo.xacro
new file mode 100644
index 0000000..e0f6368
--- /dev/null
+++ b/description/gazebo/gazebo.xacro
@@ -0,0 +1,496 @@
+
+
+
+
+
+
+ gz_ros2_control/GazeboSimSystem
+
+
+ 0
+ 270
+ 90
+ 1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+ 1
+ 270
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 2
+ 270
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 3
+ 300
+ 150
+ 1
+ 1
+ 0
+ 300
+ 150
+
+
+
+
+ 4
+ 300
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 5
+ 300
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 6
+ 300
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 7
+ 300
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 8
+ 300
+ 0
+ 1
+ 0.25
+ 0
+ 180
+ 0
+
+
+
+
+
+
+ gz_ros2_control/GazeboSimSystem
+
+
+ 0
+ 270
+ 90
+ -1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+ 1
+ 270
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 2
+ 270
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 3
+ 300
+ 150
+ -1
+ 1
+ 0
+ 300
+ 150
+
+
+
+
+ 4
+ 300
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 5
+ 300
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 6
+ 300
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 7
+ 300
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 8
+ 300
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+
+
+ gz_ros2_control/GazeboSimSystem
+
+
+ 0
+ 270
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 1
+ 270
+ 0
+ 1
+ 1
+ 0
+ 180
+ 0
+
+
+
+
+ 2
+ 270
+ 135
+ 1
+ 1
+ 0
+ 270
+ 135
+
+
+
+
+ 3
+ 270
+ 90
+ 1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+ 4
+ 270
+ 90
+ 1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+ 5
+ 270
+ 90
+ 1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+ 6
+ 300
+ 90
+ 1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+ 7
+ 300
+ 90
+ 1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+ 8
+ 180
+ 90
+ 1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+ 9
+ 180
+ 90
+ 1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+ 10
+ 180
+ 90
+ 1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+ 11
+ 180
+ 90
+ 1
+ 1
+ 0
+ 180
+ 90
+
+
+
+
+
+
+
+ 10
+ sensors/left_arm
+
+
+
+
+
+
+
+
+
+ 10
+ sensors/right_arm
+
+
+
+
+
+
+
+
+
+
+ true
+ 30.0
+ true
+
+ /realsense/realsense2_camera/color/image_raw
+
+ 1.047
+
+ 640
+ 480
+ R8G8B8
+
+
+ 0.1
+ 100.0
+
+
+
+
+
+
+ true
+ 30.0
+ true
+
+ /realsense/realsense2_camera/aligned_depth_to_color/image_raw
+
+ 1.047
+
+ 640
+ 480
+ R8G8B8
+
+
+ 0.1
+ 100.0
+
+
+
+
+
+
+ true
+ 30.0
+ true
+
+ /camera/internal/left_eye/jpg
+
+ 1.047
+
+ 640
+ 480
+ R8G8B8
+
+
+ 0.1
+ 100.0
+
+
+
+
+
+
+ true
+ 30.0
+ true
+
+ /camera/internal/right_eye/jpg
+
+ 1.047
+
+ 640
+ 480
+ R8G8B8
+
+
+ 0.1
+ 100.0
+
+
+
+
+
+
+
+ $(arg controller_config)
+
+
+
+
+
\ No newline at end of file
diff --git a/description/gazebo/gazebo_bridge.yaml b/description/gazebo/gazebo_bridge.yaml
new file mode 100644
index 0000000..5915ca3
--- /dev/null
+++ b/description/gazebo/gazebo_bridge.yaml
@@ -0,0 +1,46 @@
+# Generated by lucy_config_generator — do not edit.
+- ros_topic_name: "/clock"
+ gz_topic_name: "/world/default/clock"
+ ros_type_name: "rosgraph_msgs/msg/Clock"
+ gz_type_name: "gz.msgs.Clock"
+ direction: GZ_TO_ROS
+# Raw frames; gazebo.launch.py republishes /ext_camera/jpg -> /ext_camera/jpg (CompressedImage) for the LCP.
+- ros_topic_name: "/ext_camera/jpg"
+ gz_topic_name: "/world/default/camera"
+ ros_type_name: "sensor_msgs/msg/Image"
+ gz_type_name: "gz.msgs.Image"
+ direction: GZ_TO_ROS
+# Raw frames; gazebo.launch.py republishes /realsense/realsense2_camera/color/image_raw -> /realsense/realsense2_camera/color/image_raw (CompressedImage) for the LCP.
+- ros_topic_name: "/realsense/realsense2_camera/color/image_raw"
+ gz_topic_name: "/realsense/realsense2_camera/color/image_raw"
+ ros_type_name: "sensor_msgs/msg/Image"
+ gz_type_name: "gz.msgs.Image"
+ direction: GZ_TO_ROS
+# Raw frames; gazebo.launch.py republishes /realsense/realsense2_camera/aligned_depth_to_color/image_raw -> /realsense/realsense2_camera/aligned_depth_to_color/image_raw (CompressedImage) for the LCP.
+- ros_topic_name: "/realsense/realsense2_camera/aligned_depth_to_color/image_raw"
+ gz_topic_name: "/realsense/realsense2_camera/aligned_depth_to_color/image_raw"
+ ros_type_name: "sensor_msgs/msg/Image"
+ gz_type_name: "gz.msgs.Image"
+ direction: GZ_TO_ROS
+# Raw frames; gazebo.launch.py republishes /camera/internal/left_eye/jpg -> /camera/internal/left_eye/jpg (CompressedImage) for the LCP.
+- ros_topic_name: "/camera/internal/left_eye/jpg"
+ gz_topic_name: "/camera/internal/left_eye/jpg"
+ ros_type_name: "sensor_msgs/msg/Image"
+ gz_type_name: "gz.msgs.Image"
+ direction: GZ_TO_ROS
+# Raw frames; gazebo.launch.py republishes /camera/internal/right_eye/jpg -> /camera/internal/right_eye/jpg (CompressedImage) for the LCP.
+- ros_topic_name: "/camera/internal/right_eye/jpg"
+ gz_topic_name: "/camera/internal/right_eye/jpg"
+ ros_type_name: "sensor_msgs/msg/Image"
+ gz_type_name: "gz.msgs.Image"
+ direction: GZ_TO_ROS
+- ros_topic_name: "sensors/left_arm"
+ gz_topic_name: "sensors/left_arm"
+ ros_type_name: "ros_gz_interfaces/msg/Float32Array"
+ gz_type_name: "gz.msgs.Float_V"
+ direction: GZ_TO_ROS
+- ros_topic_name: "sensors/right_arm"
+ gz_topic_name: "sensors/right_arm"
+ ros_type_name: "ros_gz_interfaces/msg/Float32Array"
+ gz_type_name: "gz.msgs.Float_V"
+ direction: GZ_TO_ROS
diff --git a/description/gazebo/inmoov_gazebo_physics.xacro b/description/gazebo/inmoov_gazebo_physics.xacro
index c1bbb2e..cddca10 100644
--- a/description/gazebo/inmoov_gazebo_physics.xacro
+++ b/description/gazebo/inmoov_gazebo_physics.xacro
@@ -3,9 +3,9 @@
use_gazebo_sim:=true. This file is pure simulation tuning: world
anchoring, friction, contact stiffness, and self-collision flags.
- The gz_ros2_control plugin itself lives next door in
- description/ros2_control/inmoov_gz_ros2_control.xacro because it is the
- ros2_control bridge into Gazebo, not a physics property.
+ The gz_ros2_control plugin itself lives next door in the generated
+ description/gazebo/gazebo.xacro because it is the ros2_control bridge
+ into Gazebo (and carries the camera sensors), not a physics property.
Collision strategy: every URDF link reuses its visual mesh as the
collision mesh (see scripts/inject_collisions.py). The macros below only
diff --git a/description/ros2_control/inmoov_gz_ros2_control.xacro b/description/ros2_control/inmoov_gz_ros2_control.xacro
deleted file mode 100644
index 72e1680..0000000
--- a/description/ros2_control/inmoov_gz_ros2_control.xacro
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
- $(arg controller_config)
-
-
-
-
-
diff --git a/description/ros2_control/inmoov_ros2_control.xacro b/description/ros2_control/inmoov_ros2_control.xacro
index 353fe7a..92369b8 100644
--- a/description/ros2_control/inmoov_ros2_control.xacro
+++ b/description/ros2_control/inmoov_ros2_control.xacro
@@ -1,25 +1,19 @@
-
-
-
- gz_ros2_control/GazeboSimSystem
-
-
- lucy_ros2_control/LucySystemHardware
-
- false
-
-
- actuators/left_arm
- lucy_hardware_interface_left_arm
-
-
-
+
+ lucy_ros2_control/LucySystemHardware
+
+ false
+
+
+ actuators/left_arm
+ lucy_hardware_interface_left_arm
+
+
0
270
@@ -157,21 +151,16 @@
-
-
- gz_ros2_control/GazeboSimSystem
-
-
- lucy_ros2_control/LucySystemHardware
-
- false
-
-
- actuators/right_arm
- lucy_hardware_interface_right_arm
-
-
-
+
+ lucy_ros2_control/LucySystemHardware
+
+ false
+
+
+ actuators/right_arm
+ lucy_hardware_interface_right_arm
+
+
0
270
@@ -309,21 +298,16 @@
-
-
- gz_ros2_control/GazeboSimSystem
-
-
- lucy_ros2_control/LucySystemHardware
-
- false
-
-
- actuators/torso
- lucy_hardware_interface_torso_head
-
-
-
+
+ lucy_ros2_control/LucySystemHardware
+
+ false
+
+
+ actuators/torso
+ lucy_hardware_interface_torso_head
+
+
0
270
@@ -506,4 +490,4 @@
-
\ No newline at end of file
+
diff --git a/description/urdf/inmoov.urdf.xacro b/description/urdf/inmoov.urdf.xacro
index 9df9a3b..eff45f5 100644
--- a/description/urdf/inmoov.urdf.xacro
+++ b/description/urdf/inmoov.urdf.xacro
@@ -17,18 +17,22 @@
-
-
+
+
+
+ - gazebo/gazebo.xacro generated gz_ros2_control plugin +
+ camera sensors so the same controllers
+ drive simulated joints -->
-
+
diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md
index e003838..ba5214f 100644
--- a/docs/DEVELOPER.md
+++ b/docs/DEVELOPER.md
@@ -10,7 +10,7 @@ ROS 2 **Humble**. For contributors who change URDF/xacro, meshes, RViz config, h
|---------|----------|
| Robot description (xacro, meshes, materials, joint limits, collisions) | `description/` |
| ros2_control hardware interfaces (real / sim / mock) | `description/ros2_control/` |
-| Gazebo (gz-sim) physics tuning + plugin | `description/gazebo/` and `description/ros2_control/inmoov_gz_ros2_control.xacro` |
+| Gazebo (gz-sim) physics tuning + generated plugin/sensors | `description/gazebo/` (`inmoov_gazebo_physics.xacro`, generated `gazebo.xacro`, `gazebo_bridge.yaml`) |
| Hardware mapping (boards, actuators, sensors) | `config/hardware/active.yaml` — see [hardware_mapping.md](hardware_mapping.md) |
| Controller parameters for this package's bringup | `config/controllers.yaml` |
| RViz saved layout | `config/inmoov_rviz.rviz` |
@@ -86,11 +86,15 @@ Common arguments are listed in the root [README.md](../README.md#quick-launches)
-
+
+
+
+
+
-
+
```
@@ -100,7 +104,8 @@ Common arguments are listed in the root [README.md](../README.md#quick-launches)
| `robot_description/urdf/robot_description.urdf.xacro` | Links, joints, visuals (``), collisions (`` or primitive), inertias, named `` definitions, and `` refs on visuals. |
| `robot_description/meshes/dae/*.dae` | Collada meshes — used by **all three renderers** as the visual mesh (Gazebo also as the source of `` colour for mesh visuals, see §6). |
| `ros2_control/inmoov_ros2_control.xacro` | `` block: selects `gz_ros2_control/GazeboSimSystem` (Gazebo) or `lucy_ros2_control/LucySystemHardware` (real + mock — `publish_actuators:=false` toggles the micro-ROS publisher off in mock). Each `` carries `` from the URDF ``; `LucySystemHardware` clamps to that envelope. The stock `gz_ros2_control` plugin in this workspace does **not** apply the clamp. |
-| `ros2_control/inmoov_gz_ros2_control.xacro` | gz-sim only — declares the `gz_ros2_control-system` plugin so `controller_manager` runs inside Gazebo with `$(arg controller_config)`. |
+| `gazebo/gazebo.xacro` | gz-sim only — **generated by `lucy_config_generator`**. Declares the `gz_ros2_control` system plugin (so `controller_manager` runs inside Gazebo) plus the simulated camera sensors. Replaces the old hand-written `inmoov_gz_ros2_control.xacro`. |
+| `gazebo/gazebo_bridge.yaml` | gz-sim only — **generated** `ros_gz_bridge` config mapping gz topics to ROS topics (clock, camera images, sensor arrays). `gazebo.launch.py` republishes the raw camera topics into the compressed topics declared under `cameras:` in `active.yaml`. |
| `gazebo/inmoov_gazebo_physics.xacro` | gz-sim only — `` on the stand, body/hand friction (`mu1/mu2`), contact stiffness (`kp/kd`), `self_collide` flags. **No colours** (see §6). |
Editing joint names or interfaces requires updating **all three** in lockstep: `inmoov_ros2_control.xacro`, `config/controllers.yaml`, `config/hardware/active.yaml`. The hardware → ros2_control generation lives in `lucy_config_generator`.
diff --git a/launch/gazebo.launch.py b/launch/gazebo.launch.py
index d69960c..77e37c7 100644
--- a/launch/gazebo.launch.py
+++ b/launch/gazebo.launch.py
@@ -24,9 +24,8 @@
import os
from pathlib import Path
-from ament_index_python.packages import get_package_share_directory
import yaml
-from launch import LaunchDescription
+from ament_index_python.packages import get_package_prefix, get_package_share_directory
from launch.actions import (
DeclareLaunchArgument,
IncludeLaunchDescription,
@@ -41,7 +40,8 @@
)
from launch_ros.actions import Node
from lucy_control_supervisor.controllers_spawn import controllers_to_spawn
-from ament_index_python.packages import get_package_prefix
+
+from launch import LaunchDescription
def _gz_ros2_control_plugin_path():
@@ -85,19 +85,57 @@ def _active_generated_files(pkg_share: str) -> dict[str, str]:
def _default_controllers_yaml(pkg_share: str, controllers_basename: str) -> str:
- cwd_candidate = (
- Path.cwd() / "src" / "inmoov_urdf" / "config" / controllers_basename
- )
+ cwd_candidate = Path.cwd() / "src" / "inmoov_urdf" / "config" / controllers_basename
if cwd_candidate.is_file():
return str(cwd_candidate.resolve())
return os.path.join(pkg_share, "config", controllers_basename)
+def _sim_camera_topics(pkg_share: str) -> list[tuple[str, str]]:
+ """LCP-facing (compressed) topics for every camera simulated in Gazebo."""
+ candidates = [
+ Path.cwd() / "src" / "inmoov_urdf" / "config" / "hardware" / "active.yaml",
+ Path(pkg_share) / "config" / "hardware" / "active.yaml",
+ ]
+ for active in candidates:
+ if not active.is_file():
+ continue
+ try:
+ data = yaml.safe_load(active.read_text(encoding="utf-8"))
+ except (OSError, yaml.YAMLError):
+ return []
+ cameras = data.get("cameras") if isinstance(data, dict) else None
+ if not isinstance(cameras, list):
+ return []
+ topics: list[tuple[str, str]] = []
+ for cam in cameras:
+ if not isinstance(cam, dict):
+ continue
+ topic = cam.get("topic")
+ if not isinstance(topic, str) or not topic.strip():
+ continue
+ compressed_topic = cam.get("compressed_topic")
+ if not isinstance(compressed_topic, str) or not compressed_topic.strip():
+ continue
+ if cam.get("external"):
+ gz_topic = cam.get("sim_gz_topic")
+ if not isinstance(gz_topic, str) or not gz_topic.strip():
+ continue
+ elif not cam.get("link"):
+ continue
+ topics.append((topic.strip(), compressed_topic.strip()))
+ return topics
+ return []
+
+
def generate_launch_description():
+ ros_distro = os.environ.get("ROS_DISTRO", "humble").lower()
pkg_share = get_package_share_directory("inmoov_urdf")
default_base = os.path.join(pkg_share, "description")
generated = _active_generated_files(pkg_share)
- default_controllers = _default_controllers_yaml(pkg_share, generated["controllers_yaml"])
+ default_controllers = _default_controllers_yaml(
+ pkg_share, generated["controllers_yaml"]
+ )
base_path_arg = DeclareLaunchArgument(
"base_path",
@@ -130,7 +168,10 @@ def generate_launch_description():
)
bridge_config_path = os.path.join(
- get_package_share_directory("inmoov_urdf"), "config", "gazebo_bridge.yaml"
+ get_package_share_directory("inmoov_urdf"),
+ "description",
+ "gazebo",
+ "gazebo_bridge.yaml",
)
bridge = Node(
package="ros_gz_bridge",
@@ -140,17 +181,47 @@ def generate_launch_description():
output="screen",
)
- camera_compressor = Node(
- package="image_transport",
- executable="republish",
- arguments=["raw", "compressed"],
- remappings=[
- ("in", "/camera/gazebo/raw"),
- ("out/compressed", "/ext_camera/jpg"),
- ],
- parameters=[{"use_sim_time": True}],
- output="screen",
- )
+ # ros_gz_bridge can only emit raw Image; the LCP wants JPEG CompressedImage.
+ # For every simulated camera (robot-mounted + external world camera), republish
+ # the bridged raw topic -> the compressed topic the LCP subscribes to.
+ def _camera_compressor(topic: str, compressed_topic: str) -> Node:
+ safe = "".join(c if c.isalnum() else "_" for c in compressed_topic).strip("_")
+ if ros_distro == "humble":
+ return Node(
+ package="image_transport",
+ executable="republish",
+ name="camera_compressor_" + safe,
+ arguments=["raw", "compressed"],
+ remappings=[
+ ("in", topic),
+ ("out/compressed", compressed_topic),
+ ],
+ parameters=[{"use_sim_time": True}],
+ output="screen",
+ )
+ else:
+ return Node(
+ package="image_transport",
+ executable="republish",
+ name="camera_compressor_" + safe,
+ remappings=[
+ ("in", topic),
+ ("out/compressed", compressed_topic),
+ ],
+ parameters=[
+ {
+ "use_sim_time": True,
+ "in_transport": "raw",
+ "out_transport": "compressed",
+ }
+ ],
+ output="screen",
+ )
+
+ camera_compressors = [
+ _camera_compressor(topic, compressed_topic)
+ for topic, compressed_topic in _sim_camera_topics(pkg_share)
+ ]
ros_lib = _gz_ros2_control_plugin_path()
gz_plugin_path = os.pathsep.join(
@@ -246,6 +317,6 @@ def spawner_actions_from_yaml(context, *args, **kwargs):
spawn_robot,
gz_sim_launch,
bridge,
- camera_compressor,
+ *camera_compressors,
]
)
diff --git a/worlds/default.sdf b/worlds/default.sdf
index 043d5c8..2a33a53 100644
--- a/worlds/default.sdf
+++ b/worlds/default.sdf
@@ -99,37 +99,5 @@
-
-
- true
- -10 -10 20 0 0.4636 0.7853
-
- 10
-
- 5.0
- 0
- thumb
-
-
-
-
-
-
-
-
-
- true
- -10 -10 20 0 0.4636 0.7853
-
- 10.0
-
-
-
-
-
-
-
-
-