ROS 2 Parameters
Introduction
Parameters in ROS 2 provide a flexible way to configure nodes at runtime without recompiling code. They enable dynamic adjustment of node behavior, making systems more adaptable to different environments and use cases. This lesson explores the parameter system in ROS 2, including declaration, access patterns, validation, and best practices.
Parameter Overview
Parameters in ROS 2 are:
- Node-specific: Each node maintains its own parameter set
- Runtime-configurable: Can be changed during execution
- Type-safe: Strong typing with support for basic data types
- Hierarchical: Can be organized in a namespace hierarchy
- Declarable: Parameters can be declared with constraints
Declaring Parameters
Basic Parameter Declaration
Parameters must be declared before use, typically in the node constructor:
import rclpy
from rclpy.node import Node
from rclpy.parameter import Parameter
class ParameterNode(Node):
def __init__(self):
super().__init__('parameter_node')
# Declare parameters with default values
self.declare_parameter('robot_name', 'my_robot')
self.declare_parameter('max_velocity', 1.0)
self.declare_parameter('control_frequency', 50)
self.declare_parameter('use_sim_time', False)
self.declare_parameter('safety_thresholds', [0.5, 1.0, 2.0])
# Access parameter values
robot_name = self.get_parameter('robot_name').value
max_velocity = self.get_parameter('max_velocity').value
control_frequency = self.get_parameter('control_frequency').value
self.get_logger().info(
f'Initialized with robot_name: {robot_name}, '
f'max_velocity: {max_velocity}, '
f'control_frequency: {control_frequency}'
)
Parameter Descriptors
For more control over parameter constraints:
from rclpy.node import Node
from rclpy.parameter import ParameterType
from rclpy.qos import qos_profile_system_default
class ConstrainedParameterNode(Node):
def __init__(self):
super().__init__('constrained_parameter_node')
# Import parameter descriptor classes
from rcl_interfaces.msg import ParameterDescriptor
from rcl_interfaces.msg import IntegerRange, FloatingPointRange
# Declare with descriptor for integer parameter
int_descriptor = ParameterDescriptor(
type=ParameterType.PARAMETER_INTEGER,
description='Control loop frequency in Hz',
additional_constraints='Must be positive',
integer_range=[IntegerRange(from_value=1, to_value=1000, step=1)]
)
self.declare_parameter('control_frequency', 50, int_descriptor)
# Declare with descriptor for float parameter
float_descriptor = ParameterDescriptor(
type=ParameterType.PARAMETER_DOUBLE,
description='Maximum allowed velocity',
additional_constraints='Must be positive',
floating_point_range=[FloatingPointRange(from_value=0.0, to_value=10.0, step=0.1)]
)
self.declare_parameter('max_velocity', 1.0, float_descriptor)
# Declare with descriptor for boolean parameter
bool_descriptor = ParameterDescriptor(
type=ParameterType.PARAMETER_BOOL,
description='Enable safety checks'
)
self.declare_parameter('enable_safety', True, bool_descriptor)
Accessing Parameter Values
Basic Access
class ParameterAccessNode(Node):
def __init__(self):
super().__init__('parameter_access_node')
# Declare parameters
self.declare_parameter('threshold', 1.0)
self.declare_parameter('robot_id', 'robot_01')
self.declare_parameter('enabled', True)
# Get single parameter
threshold = self.get_parameter('threshold').value
robot_id = self.get_parameter('robot_id').value
enabled = self.get_parameter('enabled').value
# Get multiple parameters at once
params = self.get_parameters(['threshold', 'robot_id', 'enabled'])
threshold_alt = params[0].value
robot_id_alt = params[1].value
enabled_alt = params[2].value
# Safely get parameter with fallback
timeout = self.get_parameter_or('timeout', Parameter('timeout', value=5.0)).value
Parameter Existence Check
def check_and_access_parameter(self, param_name, default_value):
"""Check if parameter exists before accessing"""
if self.has_parameter(param_name):
return self.get_parameter(param_name).value
else:
# Declare with default if not exists
self.declare_parameter(param_name, default_value)
return self.get_parameter(param_name).value
Parameter Callbacks
On-Set Parameter Callbacks
React to parameter changes:
from rcl_interfaces.msg import SetParametersResult
class CallbackParameterNode(Node):
def __init__(self):
super().__init__('callback_parameter_node')
# Declare initial parameters
self.declare_parameter('kp', 1.0)
self.declare_parameter('ki', 0.1)
self.declare_parameter('kd', 0.01)
# Add callback for parameter changes
self.add_on_set_parameters_callback(self.parameters_callback)
# Initialize controller with current values
self.update_controller_gains()
def parameters_callback(self, params):
"""Callback for parameter changes"""
result = SetParametersResult()
result.successful = True
# Process each parameter change
for param in params:
if param.name == 'kp' and param.type_ == Parameter.Type.DOUBLE:
if param.value < 0 or param.value > 10:
result.successful = False
result.reason = 'kp must be between 0 and 10'
return result
self.get_logger().info(f'Updating Kp to: {param.value}')
elif param.name == 'ki' and param.type_ == Parameter.Type.DOUBLE:
if param.value < 0 or param.value > 1:
result.successful = False
result.reason = 'ki must be between 0 and 1'
return result
self.get_logger().info(f'Updating Ki to: {param.value}')
# Update controller if all parameters are valid
if result.successful:
self.update_controller_gains()
return result
def update_controller_gains(self):
"""Update controller with current parameter values"""
kp = self.get_parameter('kp').value
ki = self.get_parameter('ki').value
kd = self.get_parameter('kd').value
# Apply gains to controller
self.get_logger().info(f'Controller updated with gains: Kp={kp}, Ki={ki}, Kd={kd}')
Parameter Types and Conversions
ROS 2 supports several parameter types:
class ParameterTypesNode(Node):
def __init__(self):
super().__init__('parameter_types_node')
# String parameter
self.declare_parameter('robot_name', 'turtlebot')
# Integer parameter
self.declare_parameter('robot_id', 1)
# Double parameter
self.declare_parameter('max_speed', 0.5)
# Boolean parameter
self.declare_parameter('use_acceleration_limits', True)
# Array parameters
self.declare_parameter('joint_limits', [1.0, 2.0, 1.5])
self.declare_parameter('color_rgba', [0.5, 0.5, 1.0, 1.0])
# Get parameters with type checking
robot_name = self.get_parameter('robot_name').value # Returns str
robot_id = self.get_parameter('robot_id').value # Returns int
max_speed = self.get_parameter('max_speed').value # Returns float
use_accel = self.get_parameter('use_acceleration_limits').value # Returns bool
joint_limits = self.get_parameter('joint_limits').value # Returns list of floats
color_rgba = self.get_parameter('color_rgba').value # Returns list of floats
Parameter Files and YAML Configuration
Using YAML Parameter Files
Create a YAML parameter file (e.g., config/robot_params.yaml):
parameter_node:
ros__parameters:
robot_name: "optimized_robot"
max_velocity: 2.5
control_frequency: 100
safety_thresholds: [0.8, 1.2, 2.5]
use_pid: true
pid_gains:
kp: 1.5
ki: 0.2
kd: 0.05
Load the parameter file in your launch file:
# launch/parameter_demo.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
config = os.path.join(
get_package_share_directory('my_package'),
'config',
'robot_params.yaml'
)
return LaunchDescription([
Node(
package='my_package',
executable='parameter_node',
name='parameter_node',
parameters=[config]
)
])
Command Line Parameter Tools
Setting Parameters from Command Line
# List all parameters of a node
ros2 param list /parameter_node
# Get a specific parameter value
ros2 param get /parameter_node robot_name
# Set a parameter value
ros2 param set /parameter_node max_velocity 3.0
# Get all parameters as YAML
ros2 param dump /parameter_node
# Load parameters from a YAML file
ros2 param load /parameter_node config.yaml
Advanced Parameter Patterns
1. Dynamic Reconfiguration Pattern
class DynamicConfigNode(Node):
def __init__(self):
super().__init__('dynamic_config_node')
# Declare parameters with common robotics settings
self.declare_parameter('linear_vel_limit', 1.0)
self.declare_parameter('angular_vel_limit', 1.0)
self.declare_parameter('acceleration_limit', 2.0)
self.declare_parameter('deceleration_limit', 3.0)
# Timer to periodically check and apply parameters
self.timer = self.create_timer(1.0, self.check_parameter_updates)
# Store previous values to detect changes
self.prev_linear_vel = self.get_parameter('linear_vel_limit').value
self.prev_angular_vel = self.get_parameter('angular_vel_limit').value
def check_parameter_updates(self):
"""Check for parameter changes and apply them"""
current_linear = self.get_parameter('linear_vel_limit').value
current_angular = self.get_parameter('angular_vel_limit').value
if current_linear != self.prev_linear_vel:
self.get_logger().info(f'Linear velocity limit changed to: {current_linear}')
# Apply new limit to controller
self.apply_velocity_limit(current_linear)
self.prev_linear_vel = current_linear
if current_angular != self.prev_angular_vel:
self.get_logger().info(f'Angular velocity limit changed to: {current_angular}')
# Apply new limit to controller
self.apply_angular_limit(current_angular)
self.prev_angular_vel = current_angular
def apply_velocity_limit(self, limit):
"""Apply velocity limit to robot controller"""
# Implementation to update controller with new limit
pass
def apply_angular_limit(self, limit):
"""Apply angular limit to robot controller"""
# Implementation to update controller with new limit
pass
2. Parameter Groups
For complex systems, group related parameters:
from rclpy.node import Node
class ComplexParameterNode(Node):
def __init__(self):
super().__init__('complex_parameter_node')
# Control parameters
self.declare_parameter('control.loop_frequency', 50)
self.declare_parameter('control.max_velocity', 1.0)
self.declare_parameter('control.max_acceleration', 2.0)
# Navigation parameters
self.declare_parameter('nav.planning_frequency', 5)
self.declare_parameter('nav.global_plan_frequency', 1)
self.declare_parameter('nav.local_plan_frequency', 10)
# Safety parameters
self.declare_parameter('safety.min_distance', 0.5)
self.declare_parameter('safety.collision_threshold', 0.2)
self.declare_parameter('safety.emergency_stop', True)
# Get all parameters in a group
control_params = self.get_parameters_by_prefix('control.')
nav_params = self.get_parameters_by_prefix('nav.')
safety_params = self.get_parameters_by_prefix('safety.')
self.get_logger().info(f'Control parameters: {control_params}')
Parameter Best Practices
1. Naming Conventions
# Good parameter naming
self.declare_parameter('robot.max_linear_velocity', 1.0)
self.declare_parameter('controller.kp_linear', 1.0)
self.declare_parameter('sensors.laser_scan_range', 10.0)
# Avoid generic names
# self.declare_parameter('value', 1.0) # Too generic
# self.declare_parameter('param1', 2.0) # Not descriptive
2. Validation and Constraints
class ValidatedParameterNode(Node):
def __init__(self):
super().__init__('validated_parameter_node')
from rcl_interfaces.msg import ParameterDescriptor
from rcl_interfaces.msg import FloatingPointRange
# Define parameter with validation
vel_descriptor = ParameterDescriptor(
type=ParameterType.PARAMETER_DOUBLE,
description='Maximum linear velocity limit',
floating_point_range=[FloatingPointRange(from_value=0.0, to_value=5.0, step=0.01)]
)
self.declare_parameter('max_linear_vel', 1.0, vel_descriptor)
3. Default Values and Documentation
def initialize_parameters(self):
"""Initialize all parameters with documented defaults"""
# Control parameters
self.declare_parameter(
'control_loop_rate',
50,
ParameterDescriptor(description='Rate of the main control loop in Hz')
)
# Robot-specific parameters
self.declare_parameter(
'robot_wheel_radius',
0.05,
ParameterDescriptor(description='Radius of robot wheels in meters')
)
# Navigation parameters
self.declare_parameter(
'min_approach_distance',
0.1,
ParameterDescriptor(description='Minimum distance to goal before considering reached')
)
Integration with rclpy
Parameters integrate seamlessly with other rclpy features:
class IntegratedParameterNode(Node):
def __init__(self):
super().__init__('integrated_parameter_node')
# Declare parameters
self.declare_parameter('publish_frequency', 10)
# Create publisher based on parameter
self.publisher = self.create_publisher(String, 'parameter_status', 10)
# Create timer based on parameter frequency
freq = self.get_parameter('publish_frequency').value
self.timer = self.create_timer(1.0/freq, self.timer_callback)
def timer_callback(self):
"""Publish parameter-dependent information"""
current_vel = self.get_parameter('max_velocity', 1.0).value
msg = String()
msg.data = f'Max velocity is currently: {current_vel}'
self.publisher.publish(msg)
Summary
Parameters provide a powerful mechanism for configuring ROS 2 nodes at runtime. By properly declaring, accessing, and responding to parameter changes, you can create flexible and adaptable robotic systems. Understanding parameter callbacks and validation is particularly important for safety-critical applications. The next lesson will cover ROS 2 Actions for more complex goal-oriented communication patterns.