๐Ÿค– Week 6: Parameters, Launch Systems, and Configuration

Advanced ROS2 Robotics Programming

๐Ÿ“š Learning Objectives

By the end of this week, you will master:

  • Parameter server configuration for intelligent robot behavior
  • Dynamic reconfiguration at runtime without restarting nodes
  • Python launch files for complex multi-node systems
  • Multi-node orchestration and coordination
  • RQT tools for parameter visualization and management

โš™๏ธ 1. Parameter Server for Intelligent Robot Configuration

What are ROS2 Parameters?

Parameters are configuration values that can be set for ROS2 nodes without recompiling code. They enable dynamic behavior modification and are essential for creating flexible, configurable robotic systems.

๐Ÿ’ก Key Concept: Parameters allow you to tune robot behavior (speed, thresholds, gains) without changing source code.

Parameter Types in ROS2

Type Description Example
bool Boolean values enable_safety: true
int Integer numbers detection_threshold: 100
double Floating-point numbers max_speed: 2.5
string Text values robot_name: "MyRobot"
array Lists of values waypoints: [1.0, 2.0, 3.0]

Step-by-Step: Building Your First Parameterized Node

๐ŸŽฏ Learning Path: We'll build a robot node in 4 progressive steps:
  1. Step 1: Simple node with one parameter
  2. Step 2: Add multiple parameters
  3. Step 3: Add parameter validation
  4. Step 4: Add runtime reconfiguration

๐Ÿ“ Step 1: Your First Parameter

Let's start with the simplest possible parameterized node - a robot that can change its speed.

๐Ÿ“„ simple_robot_step1.py
import rclpy from rclpy.node import Node class SimpleRobot(Node): def __init__(self): super().__init__('simple_robot') # Step 1: Declare a single parameter with a default value self.declare_parameter('speed', 1.0) # Get the parameter value self.speed = self.get_parameter('speed').value # Print it to verify self.get_logger().info(f'Robot initialized with speed: {self.speed} m/s') # Create a timer to demonstrate using the parameter self.create_timer(2.0, self.timer_callback) def timer_callback(self): self.get_logger().info(f'Moving at {self.speed} m/s') def main(args=None): rclpy.init(args=args) robot = SimpleRobot() rclpy.spin(robot) robot.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()

๐Ÿงช Test Step 1

Run with default value:

ros2 run my_package simple_robot_step1

Expected output:

[INFO] [simple_robot]: Robot initialized with speed: 1.0 m/s [INFO] [simple_robot]: Moving at 1.0 m/s [INFO] [simple_robot]: Moving at 1.0 m/s

Run with custom value:

ros2 run my_package simple_robot_step1 --ros-args -p speed:=2.5

Expected output:

[INFO] [simple_robot]: Robot initialized with speed: 2.5 m/s [INFO] [simple_robot]: Moving at 2.5 m/s

๐Ÿ“ Step 2: Multiple Parameters with Descriptions

Now let's add more parameters and make them self-documenting.

๐Ÿ“„ simple_robot_step2.py
import rclpy from rclpy.node import Node from rcl_interfaces.msg import ParameterDescriptor class SimpleRobot(Node): def __init__(self): super().__init__('simple_robot') # Step 2: Declare multiple parameters with descriptions self.declare_parameter( 'speed', 1.0, ParameterDescriptor(description='Robot maximum speed in m/s') ) self.declare_parameter( 'robot_name', 'MyRobot', ParameterDescriptor(description='Name identifier for this robot') ) self.declare_parameter( 'enable_safety', True, ParameterDescriptor(description='Enable safety features') ) # Get all parameter values self.speed = self.get_parameter('speed').value self.robot_name = self.get_parameter('robot_name').value self.enable_safety = self.get_parameter('enable_safety').value # Print configuration self.get_logger().info(f'=== {self.robot_name} Configuration ===') self.get_logger().info(f'Speed: {self.speed} m/s') self.get_logger().info(f'Safety: {"ENABLED" if self.enable_safety else "DISABLED"}') self.create_timer(2.0, self.timer_callback) def timer_callback(self): status = "SAFE" if self.enable_safety else "UNSAFE" self.get_logger().info(f'[{self.robot_name}] Moving at {self.speed} m/s - {status} mode') def main(args=None): rclpy.init(args=args) robot = SimpleRobot() rclpy.spin(robot) robot.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()

๐Ÿงช Test Step 2

Run with multiple parameters:

ros2 run my_package simple_robot_step2 --ros-args \ -p speed:=3.0 \ -p robot_name:="Explorer" \ -p enable_safety:=false

Check parameter descriptions:

ros2 param describe /simple_robot speed

Expected output:

Parameter name: speed Type: double Description: Robot maximum speed in m/s Constraints:

๐Ÿ“ Step 3: Add Parameter Validation

Let's add validation to prevent invalid values that could damage the robot.

๐Ÿ“„ simple_robot_step3.py
import rclpy from rclpy.node import Node from rcl_interfaces.msg import ParameterDescriptor, SetParametersResult class SimpleRobot(Node): def __init__(self): super().__init__('simple_robot') # Declare parameters self.declare_parameter('speed', 1.0, ParameterDescriptor(description='Robot speed (0.1-5.0 m/s)')) self.declare_parameter('robot_name', 'MyRobot') self.declare_parameter('enable_safety', True) # Get initial values self.speed = self.get_parameter('speed').value self.robot_name = self.get_parameter('robot_name').value self.enable_safety = self.get_parameter('enable_safety').value # Step 3: Add parameter callback for validation self.add_on_set_parameters_callback(self.parameter_callback) self.get_logger().info(f'Robot {self.robot_name} ready!') self.create_timer(2.0, self.timer_callback) def parameter_callback(self, params): """Validate parameter changes""" result = SetParametersResult() result.successful = True for param in params: if param.name == 'speed': # Validate speed is in safe range if param.value < 0.1 or param.value > 5.0: result.successful = False result.reason = f'Speed must be between 0.1 and 5.0, got {param.value}' self.get_logger().error(result.reason) else: self.speed = param.value self.get_logger().info(f'โœ“ Speed updated to {self.speed} m/s') elif param.name == 'robot_name': # Validate name is not empty if len(param.value) == 0: result.successful = False result.reason = 'Robot name cannot be empty' self.get_logger().error(result.reason) else: self.robot_name = param.value self.get_logger().info(f'โœ“ Name updated to {self.robot_name}') elif param.name == 'enable_safety': self.enable_safety = param.value self.get_logger().warn(f'โš  Safety {"ENABLED" if param.value else "DISABLED"}') return result def timer_callback(self): self.get_logger().info(f'[{self.robot_name}] Speed: {self.speed} m/s') def main(args=None): rclpy.init(args=args) robot = SimpleRobot() rclpy.spin(robot) robot.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()

๐Ÿงช Test Step 3 - Validation

Test 1: Try to set invalid speed (should fail)

# In terminal 1: Run the robot ros2 run my_package simple_robot_step3 # In terminal 2: Try invalid speed ros2 param set /simple_robot speed 10.0

Expected output:

[ERROR] [simple_robot]: Speed must be between 0.1 and 5.0, got 10.0 Set parameter failed

Test 2: Set valid speed (should succeed)

ros2 param set /simple_robot speed 2.5

Expected output:

[INFO] [simple_robot]: โœ“ Speed updated to 2.5 m/s Set parameter successful

๐Ÿ“ Step 4: Complete Robot with Runtime Reconfiguration

Finally, let's create a complete robot that responds to sensor data and runtime parameter changes.

๐Ÿ“„ configurable_robot.py (Complete)
import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist from sensor_msgs.msg import LaserScan from rcl_interfaces.msg import ParameterDescriptor class ConfigurableRobot(Node): def __init__(self): super().__init__('configurable_robot') # Declare parameters with descriptors self.declare_parameter( 'max_speed', 1.0, ParameterDescriptor(description='Maximum robot speed (m/s)') ) self.declare_parameter( 'safety_distance', 0.5, ParameterDescriptor(description='Minimum safe distance from obstacles (m)') ) self.declare_parameter( 'detection_threshold', 100, ParameterDescriptor(description='Object detection sensitivity') ) self.declare_parameter( 'enable_safety', True, ParameterDescriptor(description='Enable/disable safety features') ) # Get initial parameter values self.max_speed = self.get_parameter('max_speed').value self.safety_distance = self.get_parameter('safety_distance').value self.detection_threshold = self.get_parameter('detection_threshold').value self.enable_safety = self.get_parameter('enable_safety').value # Create publishers and subscribers self.cmd_vel_pub = self.create_publisher(Twist, '/cmd_vel', 10) self.scan_sub = self.create_subscription( LaserScan, '/scan', self.scan_callback, 10 ) # Add parameter callback for runtime changes self.add_on_set_parameters_callback(self.parameter_callback) self.get_logger().info(f'Robot initialized with speed: {self.max_speed} m/s') def parameter_callback(self, params): """Handle parameter changes at runtime""" for param in params: if param.name == 'max_speed': self.max_speed = param.value self.get_logger().info(f'Max speed updated to: {self.max_speed}') elif param.name == 'safety_distance': self.safety_distance = param.value self.get_logger().info(f'Safety distance updated to: {self.safety_distance}') elif param.name == 'detection_threshold': self.detection_threshold = param.value self.get_logger().info(f'Detection threshold updated to: {self.detection_threshold}') elif param.name == 'enable_safety': self.enable_safety = param.value self.get_logger().info(f'Safety features: {"enabled" if self.enable_safety else "disabled"}') from rcl_interfaces.msg import SetParametersResult return SetParametersResult(successful=True) def scan_callback(self, msg): """Process laser scan data with configurable behavior""" # Find minimum distance min_distance = min(msg.ranges) twist = Twist() if self.enable_safety and min_distance < self.safety_distance: # Stop if too close to obstacle twist.linear.x = 0.0 self.get_logger().warn(f'Obstacle detected at {min_distance:.2f}m - STOPPING') else: # Move forward at configured speed twist.linear.x = self.max_speed self.cmd_vel_pub.publish(twist) def main(args=None): rclpy.init(args=args) robot = ConfigurableRobot() rclpy.spin(robot) robot.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()

Setting Parameters via Command Line

๐Ÿ–ฅ๏ธ Terminal Commands
# Run node with custom parameters ros2 run my_package configurable_robot --ros-args \ -p max_speed:=2.5 \ -p safety_distance:=0.8 \ -p detection_threshold:=150 # View current parameters ros2 param list /configurable_robot # Get specific parameter value ros2 param get /configurable_robot max_speed # Set parameter at runtime ros2 param set /configurable_robot max_speed 3.0 # Dump all parameters to file ros2 param dump /configurable_robot --output-dir ./config

Using YAML Configuration Files

๐Ÿ“„ robot_config.yaml
configurable_robot: ros__parameters: max_speed: 2.0 safety_distance: 0.6 detection_threshold: 120 enable_safety: true robot_name: "Explorer-1"
๐Ÿ–ฅ๏ธ Loading Parameters from YAML
# Run with YAML configuration ros2 run my_package configurable_robot --ros-args \ --params-file ./config/robot_config.yaml

๐ŸŽฏ Practice Exercise

Create a node with the following parameters:

  • Robot name (string)
  • Maximum speed (double, 0.1 to 5.0 m/s)
  • Turn rate (double, in rad/s)
  • Enable autonomous mode (boolean)

Test changing parameters at runtime using ros2 param set

๐Ÿš€ 2. Python Launch Files for Complex Systems

Why Launch Files?

Launch files allow you to start multiple nodes with specific configurations in a single command. They're essential for complex robotic systems that require coordination between many components.

Benefits of Launch Files:

  • Start multiple nodes simultaneously
  • Set parameters for each node
  • Configure remappings and namespaces
  • Manage node dependencies and lifecycle
  • Create reusable system configurations

Benefits of Launch Files:

  • Start multiple nodes simultaneously
  • Set parameters for each node
  • Configure remappings and namespaces
  • Manage node dependencies and lifecycle
  • Create reusable system configurations
๐ŸŽฏ Learning Path: We'll build launch files progressively:
  1. Step 1: Launch a single node
  2. Step 2: Launch with parameters
  3. Step 3: Launch multiple nodes
  4. Step 4: Use YAML configuration files
  5. Step 5: Add launch arguments for flexibility

๐Ÿ“ Step 1: Your First Launch File

Let's create the simplest possible launch file that starts one node.

๐Ÿ“„ step1_single_node.launch.py
from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): return LaunchDescription([ Node( package='my_package', executable='simple_robot_step1', name='my_robot' ) ])

๐Ÿงช Test Step 1

# Run the launch file ros2 launch my_package step1_single_node.launch.py # Verify the node is running ros2 node list

Expected output:

/my_robot

๐Ÿ“ Step 2: Launch with Parameters

Now let's add parameters to configure the node at startup.

๐Ÿ“„ step2_with_parameters.launch.py
from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): return LaunchDescription([ Node( package='my_package', executable='simple_robot_step2', name='explorer_bot', output='screen', # Show output in terminal parameters=[{ 'speed': 2.0, 'robot_name': 'Explorer-1', 'enable_safety': True }] ) ])

๐Ÿงช Test Step 2

ros2 launch my_package step2_with_parameters.launch.py

Expected output:

[INFO] [explorer_bot]: === Explorer-1 Configuration === [INFO] [explorer_bot]: Speed: 2.0 m/s [INFO] [explorer_bot]: Safety: ENABLED

๐Ÿ“ Step 3: Launch Multiple Nodes

Let's launch multiple nodes that work together.

๐Ÿ“„ step3_multi_node.launch.py
from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): # Define robot navigator node navigator = Node( package='my_package', executable='simple_robot_step3', name='navigator', output='screen', parameters=[{ 'speed': 1.5, 'robot_name': 'Navigator', 'enable_safety': True }] ) # Define sensor processor node sensor = Node( package='my_package', executable='sensor_processor', name='sensor_handler', output='screen', parameters=[{ 'rate': 10.0, 'threshold': 100 }] ) # Define controller node controller = Node( package='my_package', executable='controller', name='motion_controller', output='screen' ) return LaunchDescription([ navigator, sensor, controller ])

๐Ÿงช Test Step 3

# Launch all three nodes ros2 launch my_package step3_multi_node.launch.py # In another terminal, check all running nodes ros2 node list

Expected output:

/navigator /sensor_handler /motion_controller

๐Ÿ“ Step 4: Using YAML Configuration Files

For complex configurations, it's better to use YAML files.

๐Ÿ“„ config/robot_params.yaml
# Robot configuration file navigator: ros__parameters: speed: 2.0 robot_name: "Navigator-Pro" enable_safety: true sensor_handler: ros__parameters: rate: 20.0 threshold: 150 filter_enabled: true motion_controller: ros__parameters: kp_linear: 1.0 kp_angular: 2.0 max_acceleration: 0.5
๐Ÿ“„ step4_with_yaml.launch.py
import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): # Get the path to our config file config_file = os.path.join( get_package_share_directory('my_package'), 'config', 'robot_params.yaml' ) return LaunchDescription([ Node( package='my_package', executable='simple_robot_step3', name='navigator', output='screen', parameters=[config_file] ), Node( package='my_package', executable='sensor_processor', name='sensor_handler', output='screen', parameters=[config_file] ), Node( package='my_package', executable='controller', name='motion_controller', output='screen', parameters=[config_file] ) ])

๐Ÿงช Test Step 4

Important: First, install the config file in your package's setup.py:

# In setup.py, add to data_files: (os.path.join('share', package_name, 'config'), glob('config/*.yaml')),

Then rebuild and launch:

cd ~/ros2_ws colcon build --packages-select my_package source install/setup.bash ros2 launch my_package step4_with_yaml.launch.py

๐Ÿ“ Step 5: Launch Arguments for Flexibility

The most powerful feature: allow users to customize the launch without editing files.

๐Ÿ“„ step5_with_arguments.launch.py
from launch import LaunchDescription from launch.actions import DeclareLaunchArgument from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node def generate_launch_description(): # Declare launch arguments speed_arg = DeclareLaunchArgument( 'speed', default_value='1.0', description='Robot maximum speed in m/s' ) robot_name_arg = DeclareLaunchArgument( 'robot_name', default_value='MyRobot', description='Name of the robot' ) safety_arg = DeclareLaunchArgument( 'enable_safety', default_value='true', description='Enable safety features' ) # Use the launch arguments in node parameters robot_node = Node( package='my_package', executable='simple_robot_step3', name='configurable_robot', output='screen', parameters=[{ 'speed': LaunchConfiguration('speed'), 'robot_name': LaunchConfiguration('robot_name'), 'enable_safety': LaunchConfiguration('enable_safety') }] ) return LaunchDescription([ speed_arg, robot_name_arg, safety_arg, robot_node ])

๐Ÿงช Test Step 5

Test 1: Use default values

ros2 launch my_package step5_with_arguments.launch.py

Test 2: Override with custom values

ros2 launch my_package step5_with_arguments.launch.py \ speed:=3.0 \ robot_name:="SpeedRacer" \ enable_safety:=false

Test 3: See available arguments

ros2 launch my_package step5_with_arguments.launch.py --show-args

Expected output:

Arguments (pass arguments as ':='): 'speed': Robot maximum speed in m/s (default: '1.0') 'robot_name': Name of the robot (default: 'MyRobot') 'enable_safety': Enable safety features (default: 'true')

Basic Python Launch File Structure

๐Ÿ“„ basic_launch.py
from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): return LaunchDescription([ Node( package='my_package', executable='configurable_robot', name='robot_node', output='screen', parameters=[{ 'max_speed': 2.0, 'safety_distance': 0.5 }] ) ])

Advanced Multi-Node Launch File

๐Ÿ“„ robot_system_launch.py
import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node def generate_launch_description(): # Get package directory pkg_dir = get_package_share_directory('my_package') # Declare launch arguments use_sim_time = DeclareLaunchArgument( 'use_sim_time', default_value='false', description='Use simulation clock if true' ) config_file = DeclareLaunchArgument( 'config_file', default_value=os.path.join(pkg_dir, 'config', 'robot_config.yaml'), description='Path to configuration file' ) # Load configuration file config_path = LaunchConfiguration('config_file') # Robot navigation node robot_node = Node( package='my_package', executable='configurable_robot', name='robot_navigator', output='screen', parameters=[config_path], remappings=[ ('/cmd_vel', '/robot/cmd_vel'), ('/scan', '/robot/scan') ] ) # Sensor processing node sensor_node = Node( package='my_package', executable='sensor_processor', name='sensor_handler', output='screen', parameters=[{ 'detection_threshold': 100, 'update_rate': 10.0 }] ) # Planning node planner_node = Node( package='my_package', executable='path_planner', name='planner', output='screen', parameters=[{ 'algorithm': 'A*', 'resolution': 0.05 }] ) # Controller node controller_node = Node( package='my_package', executable='motion_controller', name='controller', output='screen', parameters=[config_path] ) return LaunchDescription([ use_sim_time, config_file, robot_node, sensor_node, planner_node, controller_node ])

Launch File with Environment Configurations

๐Ÿ“„ multi_environment_launch.py
import os from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, OpaqueFunction from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node from ament_index_python.packages import get_package_share_directory def launch_setup(context, *args, **kwargs): # Get environment type from launch argument environment = LaunchConfiguration('environment').perform(context) # Define configuration profiles for different environments configs = { 'indoor': { 'max_speed': 1.0, 'safety_distance': 0.5, 'detection_threshold': 150, 'enable_safety': True }, 'outdoor': { 'max_speed': 3.0, 'safety_distance': 1.0, 'detection_threshold': 100, 'enable_safety': True }, 'warehouse': { 'max_speed': 1.5, 'safety_distance': 0.7, 'detection_threshold': 120, 'enable_safety': True }, 'racing': { 'max_speed': 5.0, 'safety_distance': 0.3, 'detection_threshold': 80, 'enable_safety': False } } # Get configuration for selected environment config = configs.get(environment, configs['indoor']) robot_node = Node( package='my_package', executable='configurable_robot', name=f'robot_{environment}', output='screen', parameters=[config] ) return [robot_node] def generate_launch_description(): environment_arg = DeclareLaunchArgument( 'environment', default_value='indoor', description='Environment type: indoor, outdoor, warehouse, or racing' ) return LaunchDescription([ environment_arg, OpaqueFunction(function=launch_setup) ])

Running Launch Files

๐Ÿ–ฅ๏ธ Launch Commands
# Basic launch ros2 launch my_package robot_system_launch.py # Launch with arguments ros2 launch my_package multi_environment_launch.py environment:=outdoor # Launch with custom config file ros2 launch my_package robot_system_launch.py \ config_file:=/path/to/custom_config.yaml # List available launch files in a package ros2 launch my_package --show-args
โœ… Best Practice: Organize launch files by system complexity:
  • basic_launch.py - Simple single-node launches
  • system_launch.py - Complete system with multiple nodes
  • simulation_launch.py - Simulator + robot system
  • hardware_launch.py - Real robot deployment

External Lab 1

External Lab 3

External Lab 4

๐Ÿ”„ 3. Dynamic Reconfiguration at Runtime

What is Dynamic Reconfiguration?

Dynamic reconfiguration allows you to change node parameters while the system is running, without restarting nodes. This is crucial for tuning robot behavior in real-time.

๐Ÿ’ก Use Cases:
  • Tuning PID controller gains while robot is running
  • Adjusting sensor thresholds based on environment
  • Enabling/disabling features on-the-fly
  • Switching between operational modes

Implementing Parameter Callbacks

๐Ÿ“„ dynamic_robot.py
import rclpy from rclpy.node import Node from rcl_interfaces.msg import SetParametersResult, Parameter class DynamicRobot(Node): def __init__(self): super().__init__('dynamic_robot') # Declare parameters with constraints self.declare_parameter('speed', 1.0) self.declare_parameter('mode', 'normal') self.declare_parameter('sensitivity', 50) # Add callback for parameter changes self.add_on_set_parameters_callback(self.on_parameter_change) # Create timer for periodic operations self.create_timer(1.0, self.timer_callback) self.get_logger().info('Dynamic Robot initialized') def on_parameter_change(self, params): """Validate and handle parameter changes""" result = SetParametersResult() result.successful = True for param in params: if param.name == 'speed': # Validate speed is within acceptable range if 0.0 <= param.value <= 5.0: self.get_logger().info(f'Speed changed to: {param.value} m/s') else: result.successful = False result.reason = f'Speed must be between 0.0 and 5.0, got {param.value}' elif param.name == 'mode': # Validate mode is one of allowed values allowed_modes = ['normal', 'aggressive', 'cautious', 'eco'] if param.value in allowed_modes: self.get_logger().info(f'Mode changed to: {param.value}') self.apply_mode_changes(param.value) else: result.successful = False result.reason = f'Mode must be one of {allowed_modes}' elif param.name == 'sensitivity': if 0 <= param.value <= 100: self.get_logger().info(f'Sensitivity changed to: {param.value}%') else: result.successful = False result.reason = 'Sensitivity must be between 0 and 100' return result def apply_mode_changes(self, mode): """Apply preset parameters based on mode""" mode_presets = { 'normal': {'speed': 1.0, 'sensitivity': 50}, 'aggressive': {'speed': 3.0, 'sensitivity': 30}, 'cautious': {'speed': 0.5, 'sensitivity': 80}, 'eco': {'speed': 0.8, 'sensitivity': 60} } if mode in mode_presets: preset = mode_presets[mode] self.set_parameters([ Parameter('speed', Parameter.Type.DOUBLE, preset['speed']), Parameter('sensitivity', Parameter.Type.INTEGER, preset['sensitivity']) ]) def timer_callback(self): # Get current parameter values speed = self.get_parameter('speed').value mode = self.get_parameter('mode').value sensitivity = self.get_parameter('sensitivity').value self.get_logger().info( f'Running in {mode} mode: speed={speed} m/s, sensitivity={sensitivity}%' )

Parameter Services

ROS2 provides services for parameter manipulation that can be called programmatically:

๐Ÿ“„ parameter_client.py
import rclpy from rclpy.node import Node from rcl_interfaces.srv import SetParameters, GetParameters from rcl_interfaces.msg import Parameter, ParameterValue, ParameterType class ParameterClient(Node): def __init__(self): super().__init__('parameter_client') # Create service clients self.set_param_client = self.create_client( SetParameters, '/dynamic_robot/set_parameters' ) self.get_param_client = self.create_client( GetParameters, '/dynamic_robot/get_parameters' ) async def set_robot_speed(self, speed): """Set robot speed parameter""" while not self.set_param_client.wait_for_service(timeout_sec=1.0): self.get_logger().info('Waiting for parameter service...') request = SetParameters.Request() param = Parameter() param.name = 'speed' param.value = ParameterValue(type=ParameterType.PARAMETER_DOUBLE, double_value=speed) request.parameters = [param] future = self.set_param_client.call_async(request) await future if future.result().results[0].successful: self.get_logger().info(f'Successfully set speed to {speed}') else: self.get_logger().error(f'Failed to set speed: {future.result().results[0].reason}') async def get_robot_parameters(self): """Get current robot parameters""" request = GetParameters.Request() request.names = ['speed', 'mode', 'sensitivity'] future = self.get_param_client.call_async(request) await future for i, name in enumerate(request.names): value = future.result().values[i] self.get_logger().info(f'{name}: {value.double_value if value.type == 3 else value.string_value if value.type == 4 else value.integer_value}')
โš ๏ธ Important: Always validate parameter values in callbacks to prevent invalid configurations that could damage the robot or cause unsafe behavior.

๐ŸŽ›๏ธ 4. RQT Tools for Parameter Management

What is RQT?

RQT is a Qt-based framework for GUI development in ROS. It provides various plugins for visualization, debugging, and runtime parameter management.

What is RQT?

RQT is a Qt-based framework for GUI development in ROS. It provides various plugins for visualization, debugging, and runtime parameter management.

๐ŸŽฏ Hands-On Tutorial: Follow these steps to master RQT tools:
  1. Step 1: Launch and explore rqt_reconfigure
  2. Step 2: Visualize data with rqt_plot
  3. Step 3: Monitor system with rqt_graph
  4. Step 4: Debug with rqt_console
  5. Step 5: Create a custom dashboard

๐Ÿ“ Step 1: Dynamic Reconfiguration with rqt_reconfigure

Scenario: You have a robot running and want to tune its speed in real-time without restarting.

๐Ÿ–ฅ๏ธ Step 1.1: Start your robot
# Terminal 1: Launch the robot ros2 run my_package simple_robot_step3 --ros-args \ -p speed:=1.0 \ -p robot_name:="TestBot"
๐Ÿ–ฅ๏ธ Step 1.2: Open rqt_reconfigure
# Terminal 2: Launch rqt_reconfigure ros2 run rqt_reconfigure rqt_reconfigure

What You'll See:

  1. Left Panel: List of nodes with parameters (look for /simple_robot)
  2. Right Panel: Interactive controls for each parameter:
    • Speed: Slider (0.1 - 5.0)
    • Robot Name: Text field
    • Enable Safety: Checkbox

๐ŸŽฏ Try This:

  1. Move the speed slider to 3.0
  2. Watch the robot's terminal - you should see: [INFO] โœ“ Speed updated to 3.0 m/s
  3. Try setting speed to 10.0 (should fail validation)
  4. You'll see an error: [ERROR] Speed must be between 0.1 and 5.0

๐Ÿ“ Step 2: Plotting Data with rqt_plot

Scenario: You want to visualize how your robot's speed changes over time.

First, let's create a simple node that publishes data to plot:

๐Ÿ“„ data_publisher.py
import rclpy from rclpy.node import Node from std_msgs.msg import Float64 import math class DataPublisher(Node): def __init__(self): super().__init__('data_publisher') self.publisher = self.create_publisher(Float64, 'robot_speed', 10) self.timer = self.create_timer(0.1, self.timer_callback) self.counter = 0.0 def timer_callback(self): msg = Float64() # Create a sine wave for demonstration msg.data = 2.0 + math.sin(self.counter) self.publisher.publish(msg) self.counter += 0.1 def main(): rclpy.init() node = DataPublisher() rclpy.spin(node) if __name__ == '__main__': main()
๐Ÿ–ฅ๏ธ Step 2.1: Start publishing data
# Terminal 1: Run the data publisher ros2 run my_package data_publisher
๐Ÿ–ฅ๏ธ Step 2.2: Launch rqt_plot
# Terminal 2: Launch plotter ros2 run rqt_plot rqt_plot

How to Use rqt_plot:

  1. In the topic field at the top, type: /robot_speed/data
  2. Click the + button (or press Enter)
  3. You should see a real-time sine wave graph!
  4. Tip: Right-click the graph for options like auto-scroll and zoom

๐ŸŽฏ Experiment:

  • Add multiple topics to compare them
  • Pause the plot to examine specific values
  • Save the plot as an image

๐Ÿ“ Step 3: System Visualization with rqt_graph

๐Ÿ–ฅ๏ธ Step 3: Launch rqt_graph
# While your nodes are running, open: ros2 run rqt_graph rqt_graph

Understanding the Graph:

  • Circles/Ovals: Represent nodes
  • Arrows: Show topic connections (publishers โ†’ subscribers)
  • Rectangles: Represent topics

๐ŸŽฏ Try Different Views:

  1. Nodes only: Shows simplified node connections
  2. Nodes/Topics (all): Shows complete system topology
  3. Nodes/Topics (active): Shows only active connections

Debugging Tip: If your nodes aren't communicating, use this to verify:

  • Are both nodes visible?
  • Is there an arrow connecting them?
  • Are the topic names correct?

๐Ÿ“ Step 4: Debugging with rqt_console

๐Ÿ–ฅ๏ธ Step 4: Launch rqt_console
ros2 run rqt_console rqt_console

Using rqt_console Effectively:

Filter by Severity:

  • DEBUG: Detailed diagnostic information
  • INFO: Normal operational messages
  • WARN: Warning messages (not errors)
  • ERROR: Error conditions
  • FATAL: Critical failures

๐ŸŽฏ Practice Exercise:

  1. Set severity filter to "WARN" or higher
  2. In another terminal, try to set an invalid parameter:
    ros2 param set /simple_robot speed 100.0
  3. Watch the ERROR message appear in rqt_console
  4. Click on the message to see full details

Search Feature:

  • Use the search box to filter messages by keyword
  • Example: Search for "speed" to see all speed-related logs

๐Ÿ“ Step 5: Create Your Custom Dashboard

๐Ÿ–ฅ๏ธ Step 5.1: Launch main RQT
rqt

Building Your Dashboard:

Step-by-Step:

  1. Add Parameter Plugin:
    • Plugins โ†’ Configuration โ†’ Dynamic Reconfigure
  2. Add Console:
    • Plugins โ†’ Logging โ†’ Console
  3. Add Plot:
    • Plugins โ†’ Visualization โ†’ Plot
  4. Add Node Graph:
    • Plugins โ†’ Introspection โ†’ Node Graph
  5. Arrange Windows:
    • Drag plugin tabs to split the screen
    • Resize each panel as needed
  6. Save Your Layout:
    • Perspectives โ†’ Save Perspective โ†’ "my_robot_dashboard"

Load Saved Layout:

# Next time, load your saved perspective rqt --perspective-file ~/.config/ros.org/rqt_gui.ini

โœ… Complete RQT Workflow Example

Here's how professionals use RQT for robot development:

  1. Launch your robot system
  2. Open your custom RQT dashboard with all plugins
  3. Monitor logs in rqt_console (bottom left)
  4. Watch system topology in rqt_graph (bottom right)
  5. Tune parameters with rqt_reconfigure (top right)
  6. Visualize results in rqt_plot (top left)
  7. Debug issues by correlating logs, parameters, and data

Key RQT Tools for Parameters

๐Ÿ“Š rqt_reconfigure - Dynamic Parameter Tuning

The most important tool for runtime parameter adjustment with a user-friendly GUI.

๐Ÿ–ฅ๏ธ Launch rqt_reconfigure
ros2 run rqt_reconfigure rqt_reconfigure

Features:

  • Real-time parameter adjustment with sliders and input fields
  • Automatic detection of parameter types and constraints
  • Visual feedback of parameter changes
  • Save/load parameter configurations
๐Ÿ“ˆ rqt_plot - Real-time Data Visualization

Plot parameter values and topic data in real-time.

๐Ÿ–ฅ๏ธ Launch rqt_plot
# Plot specific topics ros2 run rqt_plot rqt_plot /robot/speed /robot/distance # Monitor parameter changes over time ros2 run rqt_plot rqt_plot /parameter_events
๐Ÿ” rqt_graph - Node and Topic Visualization

Visualize the ROS2 computation graph showing nodes, topics, and their connections.

๐Ÿ–ฅ๏ธ Launch rqt_graph
ros2 run rqt_graph rqt_graph

Useful for understanding system architecture and debugging communication issues.

๐Ÿ–ฅ๏ธ rqt_console - Log Message Viewer

View and filter log messages from all nodes in one place.

๐Ÿ–ฅ๏ธ Launch rqt_console
ros2 run rqt_console rqt_console

Features:

  • Filter messages by severity (DEBUG, INFO, WARN, ERROR, FATAL)
  • Search and filter by node name or message content
  • Highlight specific message patterns
  • Export logs to file
๐ŸŽฎ rqt_robot_steering - Manual Robot Control

GUI for manually controlling robot movement.

๐Ÿ–ฅ๏ธ Launch rqt_robot_steering
ros2 run rqt_robot_steering rqt_robot_steering

Provides sliders for linear and angular velocity control, publishing to /cmd_vel topic.

Complete RQT Interface

๐Ÿ–ฅ๏ธ Launch Full RQT with All Plugins
# Launch main rqt interface rqt # You can then add plugins from: Plugins menu # - Configuration > Dynamic Reconfigure # - Introspection > Node Graph # - Logging > Console # - Topics > Message Publisher # - Visualization > Plot
โœ… Pro Tip: Save your RQT perspective (window layout and plugins) for quick access:
Perspectives โ†’ Save Perspective โ†’ "my_robot_debug"

Creating Custom RQT Plugin (Advanced)

๐Ÿ“„ my_rqt_plugin.py
from rqt_gui_py.plugin import Plugin from python_qt_binding.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel class MyRobotControl(Plugin): def __init__(self, context): super(MyRobotControl, self).__init__(context) self.setObjectName('MyRobotControl') # Create main widget self._widget = QWidget() layout = QVBoxLayout() # Add status label self.status_label = QLabel('Robot Status: OK') layout.addWidget(self.status_label) # Add control buttons start_btn = QPushButton('Start Robot') start_btn.clicked.connect(self.start_robot) layout.addWidget(start_btn) stop_btn = QPushButton('Stop Robot') stop_btn.clicked.connect(self.stop_robot) layout.addWidget(stop_btn) self._widget.setLayout(layout) context.add_widget(self._widget) def start_robot(self): # Call parameter service to start robot self.status_label.setText('Robot Status: Running') def stop_robot(self): # Call parameter service to stop robot self.status_label.setText('Robot Status: Stopped')

๐ŸŽฏ Hands-on Project 6.1: Configurable Intelligent Robot System

Project Overview

Build a complete configurable robot system with multiple nodes, runtime parameter tuning, and environment-specific configurations.

๐ŸŽฏ Hands-on Project 6.1: Configurable Intelligent Robot System

Project Overview

Build a complete configurable robot system with multiple nodes, runtime parameter tuning, and environment-specific configurations.

๐Ÿ“š Complete Step-by-Step Guide

Follow this detailed walkthrough to complete the project successfully. Each step builds on the previous one.

๐Ÿ”จ Detailed Implementation Walkthrough

Phase 1: Project Setup (15 minutes)

Step 1.1: Create ROS2 Package

cd ~/ros2_ws/src ros2 pkg create --build-type ament_python intelligent_robot \ --dependencies rclpy geometry_msgs sensor_msgs std_msgs cd intelligent_robot

Step 1.2: Create Directory Structure

mkdir -p config launch touch intelligent_robot/robot_node.py touch intelligent_robot/sensor_node.py touch intelligent_robot/safety_node.py touch intelligent_robot/telemetry_node.py

Step 1.3: Update setup.py

Edit setup.py and add:

import os from glob import glob from setuptools import setup package_name = 'intelligent_robot' setup( name=package_name, version='0.0.1', packages=[package_name], data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), # Add launch files (os.path.join('share', package_name, 'launch'), glob('launch/*.py')), # Add config files (os.path.join('share', package_name, 'config'), glob('config/*.yaml')), ], install_requires=['setuptools'], zip_safe=True, maintainer='your_name', maintainer_email='your_email@example.com', description='Intelligent configurable robot system', license='Apache-2.0', tests_require=['pytest'], entry_points={ 'console_scripts': [ 'robot_node = intelligent_robot.robot_node:main', 'sensor_node = intelligent_robot.sensor_node:main', 'safety_node = intelligent_robot.safety_node:main', 'telemetry_node = intelligent_robot.telemetry_node:main', ], }, )

Phase 2: Main Robot Node (30 minutes)

Step 2.1: Create robot_node.py

This is your main navigation node with full parameter support.

๐Ÿ“„ intelligent_robot/robot_node.py
#!/usr/bin/env python3 import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist from sensor_msgs.msg import LaserScan from rcl_interfaces.msg import ParameterDescriptor, SetParametersResult class RobotNode(Node): def __init__(self): super().__init__('robot_navigator') # Declare all required parameters with descriptions self.declare_parameter('max_speed', 1.0, ParameterDescriptor(description='Maximum linear velocity (0.1-5.0 m/s)')) self.declare_parameter('safety_distance', 0.5, ParameterDescriptor(description='Minimum obstacle clearance (0.1-2.0 m)')) self.declare_parameter('detection_threshold', 100, ParameterDescriptor(description='Sensor detection sensitivity (0-255)')) self.declare_parameter('turn_rate', 1.0, ParameterDescriptor(description='Maximum angular velocity (0.1-3.0 rad/s)')) self.declare_parameter('enable_safety', True, ParameterDescriptor(description='Enable/disable safety features')) self.declare_parameter('robot_name', 'IntelligentRobot', ParameterDescriptor(description='Robot identifier')) # Get parameter values self.max_speed = self.get_parameter('max_speed').value self.safety_distance = self.get_parameter('safety_distance').value self.detection_threshold = self.get_parameter('detection_threshold').value self.turn_rate = self.get_parameter('turn_rate').value self.enable_safety = self.get_parameter('enable_safety').value self.robot_name = self.get_parameter('robot_name').value # Add parameter callback self.add_on_set_parameters_callback(self.parameter_callback) # Publishers and subscribers self.cmd_vel_pub = self.create_publisher(Twist, 'cmd_vel', 10) self.scan_sub = self.create_subscription( LaserScan, 'scan', self.scan_callback, 10) # Status timer self.create_timer(1.0, self.status_callback) self.get_logger().info(f'๐Ÿค– {self.robot_name} initialized!') self.get_logger().info(f' Max Speed: {self.max_speed} m/s') self.get_logger().info(f' Safety Distance: {self.safety_distance} m') self.get_logger().info(f' Safety: {"ENABLED" if self.enable_safety else "DISABLED"}') def parameter_callback(self, params): """Validate and apply parameter changes""" result = SetParametersResult() result.successful = True for param in params: if param.name == 'max_speed': if 0.1 <= param.value <= 5.0: self.max_speed = param.value self.get_logger().info(f'โœ“ Max speed: {param.value} m/s') else: result.successful = False result.reason = f'Speed must be 0.1-5.0, got {param.value}' elif param.name == 'safety_distance': if 0.1 <= param.value <= 2.0: self.safety_distance = param.value self.get_logger().info(f'โœ“ Safety distance: {param.value} m') else: result.successful = False result.reason = f'Distance must be 0.1-2.0, got {param.value}' elif param.name == 'detection_threshold': if 0 <= param.value <= 255: self.detection_threshold = param.value self.get_logger().info(f'โœ“ Threshold: {param.value}') else: result.successful = False result.reason = f'Threshold must be 0-255, got {param.value}' elif param.name == 'turn_rate': if 0.1 <= param.value <= 3.0: self.turn_rate = param.value self.get_logger().info(f'โœ“ Turn rate: {param.value} rad/s') else: result.successful = False result.reason = f'Turn rate must be 0.1-3.0, got {param.value}' elif param.name == 'enable_safety': self.enable_safety = param.value status = "ENABLED" if param.value else "DISABLED" self.get_logger().warn(f'โš  Safety {status}') elif param.name == 'robot_name': if len(param.value) > 0: self.robot_name = param.value self.get_logger().info(f'โœ“ Name: {param.value}') else: result.successful = False result.reason = 'Robot name cannot be empty' return result def scan_callback(self, msg): """Process laser scan and control robot""" # Find minimum distance valid_ranges = [r for r in msg.ranges if r > 0.0] if not valid_ranges: return min_distance = min(valid_ranges) twist = Twist() if self.enable_safety and min_distance < self.safety_distance: # Obstacle detected - stop twist.linear.x = 0.0 twist.angular.z = self.turn_rate # Turn to avoid self.get_logger().warn(f'โš  Obstacle at {min_distance:.2f}m - Turning!') else: # Clear path - move forward twist.linear.x = self.max_speed twist.angular.z = 0.0 self.cmd_vel_pub.publish(twist) def status_callback(self): """Periodic status report""" safety = "๐Ÿ›ก๏ธ SAFE" if self.enable_safety else "โšก PERFORMANCE" self.get_logger().info( f'[{self.robot_name}] {safety} | Speed: {self.max_speed} m/s' ) def main(args=None): rclpy.init(args=args) node = RobotNode() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()

Step 2.2: Test the Robot Node

cd ~/ros2_ws colcon build --packages-select intelligent_robot source install/setup.bash # Test with default parameters ros2 run intelligent_robot robot_node # Test with custom parameters ros2 run intelligent_robot robot_node --ros-args \ -p max_speed:=2.0 \ -p robot_name:="Explorer"

Phase 3: Configuration Files (20 minutes)

Step 3.1: Create Indoor Configuration

๐Ÿ“„ config/indoor_config.yaml
robot_navigator: ros__parameters: max_speed: 1.0 safety_distance: 0.5 detection_threshold: 150 turn_rate: 1.0 enable_safety: true robot_name: "IndoorBot" sensor_handler: ros__parameters: update_rate: 10.0 filter_enabled: true safety_monitor: ros__parameters: emergency_stop_distance: 0.3 warning_distance: 0.7 telemetry_publisher: ros__parameters: publish_rate: 1.0 include_diagnostics: true

Step 3.2: Create Other Environment Configs

Create similar files for each environment:

  • outdoor_config.yaml - Higher speeds, larger distances
  • warehouse_config.yaml - Medium settings, optimized for structured environment
  • competition_config.yaml - Maximum performance, minimal safety

Phase 4: Launch Files (25 minutes)

Step 4.1: Create Main Launch File

๐Ÿ“„ launch/robot_system.launch.py
#!/usr/bin/env python3 import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import DeclareLaunchArgument from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node def generate_launch_description(): # Get package directory pkg_dir = get_package_share_directory('intelligent_robot') # Declare launch argument for config file config_file_arg = DeclareLaunchArgument( 'config_file', default_value=os.path.join(pkg_dir, 'config', 'indoor_config.yaml'), description='Path to robot configuration file' ) config_file = LaunchConfiguration('config_file') # Define all nodes robot_node = Node( package='intelligent_robot', executable='robot_node', name='robot_navigator', output='screen', parameters=[config_file] ) sensor_node = Node( package='intelligent_robot', executable='sensor_node', name='sensor_handler', output='screen', parameters=[config_file] ) safety_node = Node( package='intelligent_robot', executable='safety_node', name='safety_monitor', output='screen', parameters=[config_file] ) telemetry_node = Node( package='intelligent_robot', executable='telemetry_node', name='telemetry_publisher', output='screen', parameters=[config_file] ) return LaunchDescription([ config_file_arg, robot_node, sensor_node, safety_node, telemetry_node ])

Step 4.2: Test the Launch File

# Rebuild after adding launch file cd ~/ros2_ws colcon build --packages-select intelligent_robot source install/setup.bash # Launch with default config (indoor) ros2 launch intelligent_robot robot_system.launch.py # Launch with outdoor config ros2 launch intelligent_robot robot_system.launch.py \ config_file:=config/outdoor_config.yaml

Phase 5: Testing & Validation (20 minutes)

๐Ÿงช Complete Testing Checklist

Test 1: Basic Functionality

# Terminal 1: Launch system ros2 launch intelligent_robot robot_system.launch.py # Terminal 2: Verify all nodes are running ros2 node list # Expected output: # /robot_navigator # /sensor_handler # /safety_monitor # /telemetry_publisher

Test 2: Parameter Changes

# View current parameters ros2 param list /robot_navigator # Try valid parameter change ros2 param set /robot_navigator max_speed 2.5 # Try invalid parameter change (should fail) ros2 param set /robot_navigator max_speed 10.0

Test 3: RQT Integration

# Open RQT reconfigure ros2 run rqt_reconfigure rqt_reconfigure # Adjust parameters using GUI and verify changes

Test 4: Environment Switching

# Test each environment configuration ros2 launch intelligent_robot robot_system.launch.py \ config_file:=config/indoor_config.yaml ros2 launch intelligent_robot robot_system.launch.py \ config_file:=config/outdoor_config.yaml ros2 launch intelligent_robot robot_system.launch.py \ config_file:=config/warehouse_config.yaml ros2 launch intelligent_robot robot_system.launch.py \ config_file:=config/competition_config.yaml

Requirements

1. Parameterized Robot Node

Create a robot node with the following configurable parameters:

  • max_speed (double): Maximum linear velocity (0.1 - 5.0 m/s)
  • safety_distance (double): Minimum obstacle clearance (0.1 - 2.0 m)
  • detection_threshold (int): Sensor detection sensitivity (0 - 255)
  • turn_rate (double): Maximum angular velocity (0.1 - 3.0 rad/s)
  • enable_safety (bool): Enable/disable safety features
  • robot_name (string): Robot identifier

2. Multi-Node Launch System

Implement a launch file that starts:

  • Main robot navigation node
  • Sensor processing node
  • Safety monitor node
  • Telemetry publisher node

All nodes should load parameters from a centralized YAML configuration file.

3. Runtime Parameter Tuning

  • Implement parameter validation callbacks
  • Support dynamic reconfiguration without node restart
  • Log all parameter changes with timestamps
  • Provide parameter bounds checking

4. Configuration Profiles

Create configuration files for different environments:

  • Indoor: Low speed, high safety
  • Outdoor: Medium speed, moderate safety
  • Warehouse: Optimized for structured environment
  • Competition: Maximum performance

Implementation Steps

Step 1: Create Package Structure
cd ~/ros2_ws/src ros2 pkg create --build-type ament_python intelligent_robot \ --dependencies rclpy geometry_msgs sensor_msgs cd intelligent_robot mkdir -p config launch
Step 2: Implement Main Robot Node

Create intelligent_robot/robot_node.py with full parameter support and validation.

Include:

  • Parameter declarations with descriptors
  • Parameter callback with validation
  • Sensor data processing using parameters
  • Velocity command publishing
Step 3: Create Configuration Files

Create YAML files in config/ directory:

  • indoor_config.yaml
  • outdoor_config.yaml
  • warehouse_config.yaml
  • competition_config.yaml
Step 4: Develop Launch Files

Create in launch/ directory:

  • single_robot.launch.py - Basic single node
  • multi_node_system.launch.py - Full system
  • environment_launch.py - Environment-specific
Step 5: Test with RQT Tools

Use RQT tools to verify functionality:

  1. Launch your robot system
  2. Open rqt_reconfigure for parameter tuning
  3. Use rqt_console to monitor logs
  4. Visualize with rqt_graph
  5. Plot data with rqt_plot

๐Ÿ“‹ Deliverables

โœ… 1. Parameterized Robot Node

  • Complete Python node with all required parameters
  • Parameter validation and error handling
  • Runtime reconfiguration support
  • Comprehensive logging

โœ… 2. Launch File System

  • Multi-node launch file
  • Environment-specific launch configurations
  • Parameter file loading
  • Launch arguments for flexibility

โœ… 3. Configuration Documentation

  • README.md with project overview
  • Parameter descriptions and valid ranges
  • Launch file usage instructions
  • Environment configuration guide
  • Troubleshooting section

Testing Checklist

๐Ÿงช Verify Your Implementation

  • โ˜ All parameters can be set via command line
  • โ˜ Parameters load correctly from YAML files
  • โ˜ Runtime parameter changes work without restart
  • โ˜ Invalid parameters are rejected with clear errors
  • โ˜ All four environment configs produce different behaviors
  • โ˜ Launch files start all nodes successfully
  • โ˜ RQT tools can connect and modify parameters
  • โ˜ Documentation is clear and complete

Bonus Challenges ๐ŸŒŸ

  1. Parameter Presets: Implement a service to switch between predefined parameter sets
  2. Auto-Tuning: Create a node that automatically adjusts parameters based on sensor feedback
  3. Parameter History: Log and visualize parameter changes over time
  4. Custom RQT Plugin: Build a custom GUI for your robot's parameters
  5. Cloud Config: Load parameters from a remote server or database

๐Ÿ“– Additional Resources

Official Documentation:

Best Practices:

  • Always provide parameter descriptors with meaningful descriptions
  • Use YAML files for complex configurations
  • Implement parameter validation to prevent invalid states
  • Document parameter ranges and units clearly
  • Use namespaces to organize parameters logically
  • Test parameter changes thoroughly before deployment

๐Ÿ”ง Troubleshooting Common Issues

โš ๏ธ Having problems? Follow these step-by-step solutions!

Problem 1: "Package not found"

Error: Package 'my_package' not found

Solution:

# Step 1: Build the package cd ~/ros2_ws colcon build --packages-select my_package # Step 2: Source the workspace source install/setup.bash # Step 3: Verify ros2 pkg list | grep my_package

Problem 2: YAML Parameters Not Loading

Common Mistake: Wrong YAML format

# โœ… CORRECT (note double underscore): node_name: ros__parameters: param1: value1 # โŒ WRONG: node_name: ros_parameters: # Missing underscore! param1: value1

Also check setup.py:

# Ensure config files are installed: data_files=[ (os.path.join('share', package_name, 'config'), glob('config/*.yaml')), ] # Then rebuild: cd ~/ros2_ws && colcon build

Problem 3: Parameters Won't Change at Runtime

Checklist - All 4 items required:

  1. โœ“ Declare: self.declare_parameter('speed', 1.0)
  2. โœ“ Add callback: self.add_on_set_parameters_callback(self.parameter_callback)
  3. โœ“ Update in callback: self.speed = param.value
  4. โœ“ Use member var: twist.linear.x = self.speed

โœ… Quick Debug Commands

# Check if ROS2 is sourced echo $ROS_DISTRO # List running nodes ros2 node list # List parameters ros2 param list /node_name # Visualize system ros2 run rqt_graph rqt_graph

๐ŸŽ“ Summary

In this week, you learned:

  • โœ… How to create fully parameterized ROS2 nodes
  • โœ… Parameter types, declaration, and validation
  • โœ… Building complex launch files with Python
  • โœ… Multi-node orchestration and coordination
  • โœ… Dynamic reconfiguration at runtime
  • โœ… Using RQT tools for parameter management
  • โœ… Creating environment-specific configurations

Next Week: We'll explore advanced topics including TF transformations, URDF robot descriptions, and robot state publishers!