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:
Step 1: Simple node with one parameter
Step 2: Add multiple parameters
Step 3: Add parameter validation
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()
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
classConfigurableRobot(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')
defparameter_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)
defscan_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)
defmain(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
# 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.
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')
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
classDynamicRobot(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')
defon_parameter_change(self, params):
"""Validate and handle parameter changes"""
result = SetParametersResult()
result.successful = Truefor param in params:
if param.name == 'speed':
# Validate speed is within acceptable rangeif0.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':
if0 <= 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
defapply_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'])
])
deftimer_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
classParameterClient(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'
)
asyncdefset_robot_speed(self, speed):
"""Set robot speed parameter"""whilenot 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}')
asyncdefget_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 inenumerate(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:
Step 1: Launch and explore rqt_reconfigure
Step 2: Visualize data with rqt_plot
Step 3: Monitor system with rqt_graph
Step 4: Debug with rqt_console
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:
Left Panel: List of nodes with parameters (look for /simple_robot)
Right Panel: Interactive controls for each parameter:
Speed: Slider (0.1 - 5.0)
Robot Name: Text field
Enable Safety: Checkbox
Try This:
Move the speed slider to 3.0
Watch the robot's terminal - you should see: [INFO] Speed updated to 3.0 m/s
Try setting speed to 10.0 (should fail validation)
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:
In the topic field at the top, type: /robot_speed/data
Click the + button (or press Enter)
You should see a real-time sine wave graph!
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:
Nodes only: Shows simplified node connections
Nodes/Topics (all): Shows complete system topology
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:
Set severity filter to "WARN" or higher
In another terminal, try to set an invalid parameter:
ros2 param set /simple_robot speed 100.0
Watch the ERROR message appear in rqt_console
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:
Add Parameter Plugin:
Plugins → Configuration → Dynamic Reconfigure
Add Console:
Plugins → Logging → Console
Add Plot:
Plugins → Visualization → Plot
Add Node Graph:
Plugins → Introspection → Node Graph
Arrange Windows:
Drag plugin tabs to split the screen
Resize each panel as needed
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:
Launch your robot system
Open your custom RQT dashboard with all plugins
Monitor logs in rqt_console (bottom left)
Watch system topology in rqt_graph (bottom right)
Tune parameters with rqt_reconfigure (top right)
Visualize results in rqt_plot (top left)
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
classMyRobotControl(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)
defstart_robot(self):
# Call parameter service to start robot
self.status_label.setText('Robot Status: Running')
defstop_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
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"
# 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!