Ros2 sim skill
Expert ROS2 Jazzy + Gazebo Harmonic (gz sim 8) robot simulation: design, build, run, verify, and analyze simulations end to end. Covers colcon workspaces, URDF/xacro authoring, SolidWorks-to-URDF export, SDF worlds, ros_gz bridge config, launch files, headless/GUI simulation, diff-drive and Ackermann mobile robots, manipulator arms (ros2_control, MoveIt 2), sensors (lidar, camera, depth, ultrasonic, IMU, GPS), RViz sensor visualization, teleop, Nav2, SLAM, rosbag recording and analysis. Enforces: interview first, search existing packages before writing code, reuse existing worlds, verify every build/launch with evidence, always ship an RViz config showing every sensor. Use for ANY task mentioning ROS, ROS2, Jazzy, Gazebo, gz sim, Ignition, URDF, xacro, SDF, RViz, colcon, tf, ros2_control, MoveIt, Nav2, SLAM, teleop, or rosbag — creating/spawning/simulating robots, adding sensors, bridging topics, writing launch files, or debugging colcon builds and Gazebo launches.From its SKILL.md
npx -y skills add BaraaLazkani/ros2-sim-skillAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
19.0 KB, ~4.9k tokens by cl100k_base, as published. Nobody here has run it
ROS2 Simulation Skill
Design, build, run, verify, and analyze ROS2 Jazzy + Gazebo Harmonic (gz sim
8) robot simulations. This is a router: it holds doctrine, workflows, and
selection tables, and points at references/*.md for depth, scripts/*
for execution, and assets/* for copy-and-adapt starting files.
The Five Laws
- Interview before building. Use
AskUserQuestionper the bank below before writing a line of URDF or launch code for a new simulation. - Search for existing packages before writing code. Run
scripts/pkg_search.sh <keyword>first — ROS2 Jazzy already ships teleop, SLAM, nav, EKF, and controller packages for almost everything. - Reuse existing worlds before authoring new ones. Check the world
catalog below before writing a single
<model>tag. - Never claim success without evidence. A clean
colcon buildexit code and printed logs are not proof a simulation works — runscripts/sim_verify.pyand report its output. - Every simulation ships an RViz config displaying every sensor. No
exceptions, not even for "just testing" — see
references/rviz-sensor-viz.md.
Environment Facts (verify on your machine)
| Fact | Detail |
|---|---|
| ROS2 distro | Jazzy, /opt/ros/jazzy |
| Simulator | gz sim 8.x (Harmonic) |
| Commonly installed | nav2, slam_toolbox, teleop_twist_keyboard/teleop_twist_joy, ros2_control + gz_ros2_control, rosbag2 (sqlite3 + mcap), rqt_plot, robot_localization — verify with env_check.sh since apt install sets vary |
| May not be installed | MoveIt (check for moveit* under /opt/ros/jazzy/share, or run env_check.sh --require moveit before promising a MoveIt-based workflow), plotjuggler, twist_mux — none of these are pulled in by a base ros-jazzy-desktop install |
GZ_SIM_RESOURCE_PATH | Unset in a bare shell on a standard apt install. Only becomes populated once your shell rc file (sourcing jazzy + your workspace overlay) has run. Don't rely on it — launch files must append their own model/world paths explicitly (AppendEnvironmentVariable, see references/launch-patterns.md). This holds for any Jazzy apt install, not just this one. |
GZ_SIM_SYSTEM_PLUGIN_PATH | Must include /opt/ros/jazzy/opt/gz_sim_vendor/lib/gz-sim-8/plugins or gz plugins (DiffDrive, sensors, ros2_control bridge) fail to load silently. This path pattern is standard for any Jazzy apt install. |
| Workspace | <your_workspace>/src (conventionally ~/ros2_ws/src) — check what packages already exist there before scaffolding anything new; run scripts/pkg_search.sh per Law 2. |
| Shell state | Claude's Bash tool does not persist shell state between calls — every ros2/gz/colcon command must source /opt/ros/jazzy/setup.bash && source ~/ros2_ws/install/setup.bash && (adjust the workspace path) in the same command. This is a Claude Code harness behavior, true on any machine. |
Run bash <skill_path>/scripts/env_check.sh at the start of any session
that will build or launch anything. It reports install status, both env
vars above, workspace overlay build state, and stale gz sim processes —
this is how you turn the table above from "typical" into "verified for
your machine."
Interview First (mandatory for new simulations)
Use AskUserQuestion before scaffolding a new simulation. Skip questions
the user already answered in their request; never skip the sensor
question — it drives Law 5.
| Question | Options |
|---|---|
| Robot type | diff-drive / Ackermann (check the workspace for an existing *_description package to reuse or mirror first — Law 2) / manipulator arm / other |
| Sensors (multiSelect) | lidar / camera / depth camera / ultrasonic / IMU / GPS / none |
| World | existing (list from the catalog below) / minimal flat / custom description |
| Goal | teleop drive-around / SLAM + mapping / nav2 autonomous nav / arm motion / sensor data collection / algorithm testing |
| Control preference (only if relevant) | quick gz plugin (DiffDrive/AckermannSteering) vs full ros2_control |
Workflow: New Simulation From Scratch
bash scripts/env_check.sh— confirm the environment is sane.- Interview (above).
bash scripts/pkg_search.sh <capability>for every capability the interview surfaced (teleop, slam, nav, ekf, ...) — Law 2.- Scaffold workspace/packages —
references/workspace-packaging.md. - Author URDF/xacro from
assets/urdf/*macros —references/urdf-authoring.md. bash scripts/urdf_check.sh <robot>.urdf.xacro— model gate.- Pick a world from the catalog below, or author one if nothing fits —
references/sdf-worlds.md. - Write the bridge yaml from
assets/config/bridge_template.yaml—references/ros-gz-bridge.md. - Write the launch file from
assets/launch/sim.launch.py.template—references/launch-patterns.md. python3 scripts/gen_rviz_config.py --output <robot>.rviz ...— Law 5.colcon build(never insidesrc/), source the overlay, launch headless first (-s), runscripts/sim_verify.py.- Fix loop on any failed gate —
references/self-correction.md, escalate toreferences/troubleshooting.mdafter 3 failed attempts on the same gate. Only then launch GUI + RViz for the user and report with evidence.
Workflow: Modify an Existing Simulation
Read the existing xacro/launch/bridge files first — don't guess their
structure. A modular xacro split (base.xacro/wheels.xacro/
<drive_type>_drive.xacro/sensors.xacro) is a proven pattern worth
mirroring if the workspace already uses it. Adding a sensor = one xacro macro
(assets/urdf/sensor_*.xacro) + one bridge yaml entry + one RViz display
flag + re-run sim_verify.py. Changing worlds = update the launch file's
world argument and re-check GZ_SIM_RESOURCE_PATH handling, not a
rewrite. Always re-run the verify gate after any change — Law 4 doesn't get
suspended for "small" edits.
Workflow: Run & Verify
| Mode | gz_args | When |
|---|---|---|
| Headless (server only) | -r -s | Always run this first — verification, evals, CI-style checks |
| Full (server + GUI) | -r | Once headless is verified, for the user to watch |
| GUI-only (attach to running server) | -g | Attaching a viewer to a server already launched headless |
Launch via the package's gz_sim.launch.py-style entry point with
run_in_background: true, capture its log, then verify:
python3 <skill_path>/scripts/sim_verify.py \
--topics /scan:5 /odom:10 /clock:10 \
--tf odom:base_link \
--model <robot_name> \
--move-test /cmd_vel
Clean up stale servers before relaunching (pgrep -f 'gz sim' /
pkill -f 'gz sim' — a new sim can silently attach to an old server and
produce misleading "it works" evidence). The final report to the user must
include an evidence block: the exact commands run and their captured
output (build exit code, sim_verify.py table, RViz display manifest) —
not a prose claim.
Workflow: Analyze Results
ros2 bag record (explicit topics or -a, mcap or sqlite3 storage) →
python3 scripts/bag_plot.py <bag_dir> --xy /odom ... --output traj.png or
--stats for min/max/mean/hz → rqt_plot for live GUI sessions. Full
recipes, ground-truth-pose bridging for accuracy metrics, and storage
hygiene in references/bag-analysis.md.
Workflow: SolidWorks → URDF
The user's SolidWorks assembly lives on their Windows machine — Claude
guides step by step, the user drives the export, then hands the exported
folder back for the Jazzy conversion (package.xml format 3, xacro
conversion, mesh unit/path fixes, adding gz sensor tags and drive plugins,
none of which the exporter provides). Full walkthrough:
references/solidworks-export.md.
Selection Tables
World catalog
| World | Path | Good for |
|---|---|---|
default (ros_gz_sim demo) | /opt/ros/jazzy/share/ros_gz_sim_demos/worlds/default.sdf | Fastest empty smoke test |
vehicle (ros_gz_sim demo) | /opt/ros/jazzy/share/ros_gz_sim_demos/worlds/vehicle.sdf | Reference for wheeled-vehicle plugin wiring |
warehouse (nav2 tb4) | /opt/ros/jazzy/share/nav2_minimal_tb4_sim/worlds/warehouse.sdf | Large-scale nav2 benchmarking |
depot (nav2 tb4) | /opt/ros/jazzy/share/nav2_minimal_tb4_sim/worlds/depot.sdf | Second nav2 reference scenario |
minimal_flat | assets/worlds/minimal_flat.sdf | Starting point for authoring a genuinely new world |
These ship with any Jazzy install that has ros_gz_sim_demos / nav2_minimal_tb4_sim
installed, plus minimal_flat which ships with this skill. Your own
workspace almost certainly has more worlds worth cataloging here — see
"Example: cataloging your own worlds" in references/sdf-worlds.md for
how to fold them in and validate reused worlds with gz sdf -k.
Full annotated catalog (sizes, obstacle detail, Fuel models, gz sdf -k
validation): references/sdf-worlds.md.
Task → existing package
| Task | Package | Status (verify with env_check.sh) |
|---|---|---|
| Keyboard/joystick teleop | teleop_twist_keyboard / teleop_twist_joy | Typically installed |
| SLAM | slam_toolbox | Typically installed |
| Autonomous navigation | nav2_bringup | Typically installed |
| Mobile/arm control | ros2_control + gz_ros2_control | Typically installed |
| Sensor fusion / EKF | robot_localization | Typically installed |
| Bag recording/replay | rosbag2 (sqlite3 + mcap) | Typically installed |
| Arm motion planning | MoveIt 2 | Often not installed by default — sudo apt install ros-jazzy-moveit ros-jazzy-moveit-setup-assistant ros-jazzy-moveit-resources (present as a command, don't run unasked) |
| Live plotting | rqt_plot / scripts/bag_plot.py | plotjuggler often not installed |
| cmd_vel multiplexing | twist_mux | Often not installed by default — sudo apt install ros-jazzy-twist-mux |
Full capability cheat table (~25 rows) and the search-first decision flow:
references/package-discovery.md.
Drive approach: gz plugin vs ros2_control
| Approach | Use when |
|---|---|
gz DiffDrive / AckermannSteering system plugin | Quick demo, no ROS controller deps needed, odom TF straight from gz |
ros2_control (diff_drive_controller / JTC) | nav2-grade tunable odometry, controller lifecycle management, required for arms |
Full parameter tables and the Jazzy diff_drive_controller TwistStamped
gotcha: references/ros2-control-mobile.md.
Sensor → SDF type → gz topic → bridge → RViz display
| Sensor | SDF <sensor type=...> | gz topic pattern | ROS type ← gz type (bridge) | RViz display |
|---|---|---|---|---|
| 2D lidar | gpu_lidar | /world/<w>/model/<r>/link/<l>/sensor/<s>/scan | sensor_msgs/msg/LaserScan ← gz.msgs.LaserScan | LaserScan (Best Effort) |
| 3D lidar | gpu_lidar | .../scan/points | sensor_msgs/msg/PointCloud2 ← gz.msgs.PointCloudPacked | PointCloud2 (Best Effort) |
| Camera (RGB) | camera | .../image | sensor_msgs/msg/Image ← gz.msgs.Image | Image (raw transport) |
| Camera info | (paired with camera) | .../camera_info | sensor_msgs/msg/CameraInfo ← gz.msgs.CameraInfo | paired with Image, not standalone |
| Ultrasonic | gpu_lidar (narrow-FOV, assets/urdf/sensor_ultrasonic.xacro) | .../scan | sensor_msgs/msg/Range ← gz.msgs.LaserScan | Range (cone alpha ~0.5) |
| IMU | imu | .../imu | sensor_msgs/msg/Imu ← gz.msgs.IMU | Imu class — may not be registered on your machine (rviz_imu_plugin is not installed by default; check with env_check.sh, install with sudo apt install ros-jazzy-rviz-imu-plugin). gen_rviz_config.py still emits the display + a warning; RViz shows "class not found" for that one display only if it's missing, everything else is unaffected. Use rqt_plot on /imu/orientation.* as the working fallback until installed. |
| GPS | navsat | .../navsat | sensor_msgs/msg/NavSatFix ← gz.msgs.NavSat | no dedicated display; log/echo |
Full type-mapping table (clock, cmd_vel, odom, joints, tf, magnetometer,
joy) and debug recipes: references/ros-gz-bridge.md. Full display recipes
and QoS pitfalls: references/rviz-sensor-viz.md.
Scripts
| Script | Invocation | When to run |
|---|---|---|
env_check.sh | bash <skill_path>/scripts/env_check.sh [--json] [--require moveit,nav2,...] [--ws PATH] | Start of every session that will build or launch; before promising anything that might not be installed |
pkg_search.sh | bash <skill_path>/scripts/pkg_search.sh [--ws PATH] <keyword> [keyword2 ...] | Before writing any node/launch logic for a capability — Law 2 |
urdf_check.sh | bash <skill_path>/scripts/urdf_check.sh <file.urdf.xacro> [--xacro-args 'k:=v ...'] [--ws PATH] | Before every Gazebo attempt — the model gate |
sim_verify.py | python3 <skill_path>/scripts/sim_verify.py [--ws PATH] [--topics T:hz ...] [--tf p:c ...] [--model NAME] [--move-test /cmd_vel] [--level MODEL[:max_rad]] [--timeout N] [--json] | After every launch, before claiming anything works — Law 4. --level checks resting roll/pitch (default max 0.05 rad) — run it once after every first spawn |
gen_rviz_config.py | python3 <skill_path>/scripts/gen_rviz_config.py --output out.rviz [--fixed-frame F] [--laser T]... [--pointcloud T]... [--image T]... [--camera-info T]... [--range T]... [--imu T]... [--map T] [--path T] [--markers T] [--no-robot-model] [--no-tf] | Before delivering any simulation — Law 5. Never hand-write .rviz YAML |
bag_plot.py | python3 <skill_path>/scripts/bag_plot.py <bag_dir> [--list] [--topic T --field F.path]... [--xy T fx fy] [--stats] --output out.png | Analysis workflow — trajectory plots, field-vs-time plots, stats |
All scripts self-source /opt/ros/jazzy/setup.bash (+ workspace overlay via
--ws, default ~/ros2_ws), support --help, and are safe to run
read-only except where noted.
Reference Guides (load as needed)
| Topic | File | When to Read |
|---|---|---|
| Package discovery | references/package-discovery.md | Before writing any custom node/logic — Law 2 doctrine + capability table |
| Workspace & packaging | references/workspace-packaging.md | Creating a package, writing CMakeLists/package.xml, deciding new-vs-existing workspace |
| URDF/xacro authoring | references/urdf-authoring.md | Any link/joint/inertia/sensor/drive-plugin work — the deepest reference |
| SolidWorks → URDF | references/solidworks-export.md | User has a SolidWorks assembly to bring in |
| SDF world authoring & reuse | references/sdf-worlds.md | Picking or authoring a world — Law 3 |
| ros_gz bridge | references/ros-gz-bridge.md | Wiring or debugging any Gazebo↔ROS topic bridge — the type-mapping bible |
| Launch patterns | references/launch-patterns.md | Writing/fixing a sim launch file, managing background gz/ros2 processes |
| Mobile base control | references/ros2-control-mobile.md | Diff-drive/Ackermann drive plugin vs ros2_control decision + wiring |
| Manipulators & MoveIt | references/manipulators-moveit.md | Simulating an arm — two-tier strategy since MoveIt often isn't installed by default |
| Nav2 + SLAM | references/nav2-slam.md | Mapping or autonomous navigation on a custom sim robot |
| RViz sensor visualization | references/rviz-sensor-viz.md | MUST READ before delivering any simulation — Law 5 enforcement |
| Self-correction | references/self-correction.md | MUST READ before claiming anything works — the evidence loop and stage gates |
| Bag analysis | references/bag-analysis.md | Recording, replaying, plotting, or computing metrics from a run |
| Troubleshooting | references/troubleshooting.md | A gate has failed the same way 3 times — symptom-indexed fixes, the escalation target |
Critical Rules & Common Mistakes
- Every
ros2/gz/colconBash call must re-source in the same command — shell state doesn't persist:source /opt/ros/jazzy/setup.bash && source ~/ros2_ws/install/setup.bash && <cmd>. - Never
colcon buildfrom insidesrc/— build from the workspace root. - Pass
use_sim_time:=trueto every node in a simulation launch, includingrviz2— otherwise expect TF extrapolation errors. - Always bridge
/clock(rosgraph_msgs/msg/Clock←gz.msgs.Clock) — it is a prerequisite foruse_sim_timeworking at all. - Spawn with a small
-z 0.1offset to avoid ground-plane collision jitter at t=0. GZ_SIM_RESOURCE_PATHis unset in a bare shell — launch files must append their own package share paths (AppendEnvironmentVariable), never assume it's inherited from the interactive shell.- Kill stale
gz simservers (pkill -f 'gz sim') before relaunching — a new launch can silently attach to an old server and produce misleading "it works" evidence. - Build xacro
Commandsubstitutions asParameterValue(Command(['xacro ', path, ...]), value_type=str)— omittingvalue_type=stris a common silent robot_state_publisher failure. - Don't reinvent teleop, nav, SLAM, or EKF fusion — Law 2 exists because Jazzy already ships working, tested packages for all of them.
visualization_msgs/Marker(Array)is not bridgeable — publish markers directly from a ROS-side node, never add a marker bridge entry.- MoveIt is frequently not installed by default — check with
env_check.sh --require moveitbefore promising a MoveIt-based workflow; default to theros2_controlJTC tier for arms unless the user explicitly wants MoveIt and accepts the install. diff_drive_controllerin Jazzy expectsTwistStampedby default — remap/adapt teleop output (stamped:=true) accordingly.- Missing
gz-sim-sensors-system(ogre2) orgz-sim-imu-systemworld plugins is the #1 cause of "sensor topic never publishes" — check the world file, not the robot's sensor tag, first. - Diff-drive bases get casters front AND rear — the support polygon
must contain the center of mass, and two wheels on one axle alone don't
constrain pitch. After first spawn, verify the robot rests level
(roll/pitch ≈ 0, see
references/self-correction.md) before trusting any sensor data — a tilted rest tilts every sensor frame with it.
What ships with it: 39 files
3425.9 KB alongside SKILL.md, 6 of them executable
.claude-plugin/
- marketplace.json544 B
- plugin.json820 B
assets/
- config/arm_controllers.yaml2.3 KB
- config/bridge_template.yaml6.5 KB
- config/diffdrive_controllers.yaml3.3 KB
- launch/display.launch.py.template3.1 KB
- launch/sim.launch.py.template7.1 KB
- rviz/mobile_sensors_example.rviz9.3 KB
- urdf/diffdrive_base.urdf.xacro11.5 KB
- urdf/inertial_macros.xacro3.7 KB
- urdf/sensor_camera.xacro5.4 KB
- urdf/sensor_imu.xacro3.7 KB
- urdf/sensor_lidar.xacro3.7 KB
- urdf/sensor_ultrasonic.xacro4.5 KB
- worlds/minimal_flat.sdf3.4 KB
docs/
- demo.gif3047.4 KB
references/
- bag-analysis.md10.7 KB
- launch-patterns.md16.8 KB
- manipulators-moveit.md11.8 KB
- nav2-slam.md10.7 KB
- package-discovery.md11.4 KB
- ros2-control-mobile.md11.7 KB
- ros-gz-bridge.md20.6 KB
- rviz-sensor-viz.md13.8 KB
- sdf-worlds.md20.2 KB
- self-correction.md11.0 KB
- solidworks-export.md20.0 KB
- troubleshooting.md27.3 KB
- urdf-authoring.md28.1 KB
- workspace-packaging.md12.8 KB
scripts/
- bag_plot.pyruns11.3 KB
- env_check.shruns9.8 KB
- gen_rviz_config.pyruns20.2 KB
- pkg_search.shruns6.4 KB
- sim_verify.pyruns14.3 KB
- urdf_check.shruns11.5 KB
- .gitignore50 B
- LICENSE1.0 KB
- README.md8.2 KB