Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Clean Architecture compatible ROS2 launch files and parameter management (Python)
.claude/skills/harunkurtdev-ros2-launch-configuration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 54% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 63% | 0% |
| case-11 | ✗→✓ | ▲ Improved | -33% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 59% | 0% |
| case-23 | ✗→✓ | ▲ Improved | 44% | 0% |
This skill provides a guide for creating modular and reusable ROS2 launch files, which are written in Python for both C++ and Python nodes.
packages/
└── robot_core/
└── launch/
├── robot_launch.py # Main launch file
├── sensors_launch.py # Sensor subsystem
├── navigation_launch.py # Navigation subsystem
└── includes/
├── common.py # Common functions
└── defaults.py # Default valuespython#!/usr/bin/env python3 """ Launch file: robot_launch.py Description: Main robot system launch file """ import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import ( DeclareLaunchArgument, IncludeLaunchDescription, GroupAction, SetEnvironmentVariable, LogInfo ) from launch.conditions import IfCondition, UnlessCondition from launch.substitutions import ( LaunchConfiguration, PathJoinSubstitution ) from launch.launch_description_sources import PythonLaunchDescriptionSource from launch_ros.actions import Node, SetParameter from launch_ros.substitutions import FindPackageShare def generate_launch_description(): """Generate launch description.""" # Package paths pkg_robot_core = get_package_share_directory('robot_core') # Launch arguments declared_arguments = [ DeclareLaunchArgument( 'robot_name', default_value='robot_1', description='Robot namespace' ), DeclareLaunchArgument( 'use_sim', default_value='false', description='Use simulation mode' ), DeclareLaunchArgument( 'config_file', default_value=os.path.join(pkg_robot_core, 'config', 'params.yaml'), description='Path to parameter file' ), ] # Configurations robot_name = LaunchConfiguration('robot_name') use_sim = LaunchConfiguration('use_sim') config_file = LaunchConfiguration('config_file') # Environment setup env_setup = [ SetEnvironmentVariable('RCUTILS_COLORIZED_OUTPUT', '1'), ] # Global parameters global_params = SetParameter(name='use_sim_time', value=use_sim) # C++ Node Example cpp_node = Node( package='robot_cpp_pkg', executable='robot_controller_cpp', # Name of the C++ executable in CMakeLists.txt name='controller', namespace=robot_name, parameters=[config_file], output='screen' ) # Python Node Example py_node = Node( package='robot_py_pkg', executable='sensor_node.py', # Name of the script or entry point name='sensors', namespace=robot_name, parameters=[config_file], output='screen', condition=UnlessCondition(use_sim) ) return LaunchDescription( declared_arguments + env_setup + [global_params, cpp_node, py_node] )
yaml# config/params.yaml /**: ros__parameters: # Global parameters use_sim_time: false log_level: "info" robot_state: ros__parameters: # Robot state node parameters update_rate: 100.0 frame_id: "base_link" # Nested parameters position_filter: type: "kalman" process_noise: 0.01 navigation: ros__parameters: max_velocity: 1.5 planner: type: "astar"
cpp// Within a ROS2 Node void load_parameters() { this->declare_parameter("update_rate", 10.0); this->declare_parameter("position_filter.type", "default"); double rate = this->get_parameter("update_rate").as_double(); std::string filter_type = this->get_parameter("position_filter.type").as_string(); RCLCPP_INFO(this->get_logger(), "Rate: %f, Filter: %s", rate, filter_type.c_str()); }
pythonfrom launch_ros.actions import LifecycleNode from launch_ros.events.lifecycle import ChangeState from lifecycle_msgs.msg import Transition from launch.actions import EmitEvent, RegisterEventHandler from launch.event_handlers import OnProcessStart def generate_launch_description(): # ... driver_node = LifecycleNode( package='robot_drivers', executable='lidar_driver', name='lidar', namespace='', output='screen' ) # Auto-configure on start configure_event = RegisterEventHandler( OnProcessStart( target_action=driver_node, on_start=[ EmitEvent(event=ChangeState( lifecycle_node_matcher=lambda n: n == driver_node, transition_id=Transition.TRANSITION_CONFIGURE, )), ] ) ) return LaunchDescription([driver_node, configure_event])
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 10,455 | 8,763 | -16% | 1 | 1 | 0% | 2,017 | 3,104 | +54% | 0 | 0 | — |
case-02 | pass→pass | 6,883 | 5,157 | -25% | 1 | 1 | 0% | 1,162 | 2,209 | +90% | 0 | 0 | — |
case-03 | pass→pass | 8,218 | 6,470 | -21% | 1 | 1 | 0% | 1,629 | 2,566 | +58% | 0 | 0 | — |
case-04 | pass→pass | 8,267 | 5,067 | -39% | 1 | 1 | 0% | 1,597 | 2,251 | +41% | 0 | 0 | — |
case-05 | pass→pass | 5,638 | 3,146 | -44% | 1 | 1 | 0% | 978 | 1,852 | +89% | 0 | 0 | — |
case-06 | pass→pass | 9,967 | 5,644 | -43% | 1 | 1 | 0% | 1,869 | 2,414 | +29% | 0 | 0 | — |
case-07 | pass→pass | 9,060 | 6,355 | -30% | 1 | 1 | 0% | 1,755 | 2,550 | +45% | 0 | 0 | — |
case-08 | pass→pass | 10,224 | 5,894 | -42% | 1 | 1 | 0% | 2,043 | 2,421 | +19% | 0 | 0 | — |
case-09 | pass→pass | 9,631 | 5,360 | -44% | 1 | 1 | 0% | 1,726 | 2,209 | +28% | 0 | 0 | — |
case-10 | fail→pass | 6,915 | 4,659 | -33% | 1 | 1 | 0% | 1,320 | 2,147 | +63% | 0 | 0 | — |
case-11 | fail→pass | 17,003 | 4,380 | -74% | 1 | 1 | 0% | 3,076 | 2,051 | -33% | 0 | 0 | — |
case-12 | pass→pass | 5,531 | 3,158 | -43% | 1 | 1 | 0% | 1,086 | 1,871 | +72% | 0 | 0 | — |
case-13 | pass→pass | 10,940 | 8,432 | -23% | 1 | 1 | 0% | 2,112 | 2,992 | +42% | 0 | 0 | — |
case-14 | pass→pass | 8,446 | 4,974 | -41% | 1 | 1 | 0% | 1,639 | 2,227 | +36% | 0 | 0 | — |
case-15 | pass→pass | 4,861 | 4,014 | -17% | 1 | 1 | 0% | 917 | 2,047 | +123% | 0 | 0 | — |
case-16 | fail→pass | 7,775 | 5,970 | -23% | 1 | 1 | 0% | 1,453 | 2,305 | +59% | 0 | 0 | — |
case-17 | pass→pass | 6,754 | 3,966 | -41% | 1 | 1 | 0% | 1,284 | 2,021 | +57% | 0 | 0 | — |
case-18 | pass→pass | 5,087 | 3,344 | -34% | 1 | 1 | 0% | 894 | 1,869 | +109% | 0 | 0 | — |
case-19 | pass→pass | 8,814 | 16,498 | +87% | 1 | 1 | 0% | 1,545 | 2,341 | +52% | 0 | 0 | — |
case-20 | pass→pass | 4,732 | 3,714 | -22% | 1 | 1 | 0% | 929 | 2,085 | +124% | 0 | 0 | — |
case-21 | pass→pass | 7,067 | 6,195 | -12% | 1 | 1 | 0% | 1,479 | 2,605 | +76% | 0 | 0 | — |
case-22 | pass→pass | 5,842 | 5,479 | -6% | 1 | 1 | 0% | 1,045 | 2,401 | +130% | 0 | 0 | — |
case-23 | fail→pass | 8,848 | 5,833 | -34% | 1 | 1 | 0% | 1,718 | 2,469 | +44% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +22 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.