ROS2 Launch Files with pykal
In the tutorials so far, we’ve been manually creating and starting nodes one by one in Jupyter notebooks. This is great for learning, but professional ROS development uses launch files to start entire systems with a single command.
This guide shows how to create Python launch files for pykal ROS systems.
Why Launch Files?
Without Launch Files (manual):
# Start each node individually
rclpy.init()
waypoint_node.create_node()
controller_node.create_node()
simulator_node.create_node()
kf_node.create_node()
waypoint_node.start()
controller_node.start()
simulator_node.start()
kf_node.start()
# Must manage lifecycle manually
With Launch Files (automated):
ros2 launch pykal_demos turtlebot_navigation.launch.py
Benefits:
✓ Start entire system with one command ✓ Configure parameters from one place ✓ Handle node dependencies and startup order ✓ Easily switch between simulation/hardware ✓ Share reproducible setups ✓ Professional standard for ROS
ROS2 Launch File Basics
ROS2 uses Python launch files (not XML like ROS1):
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='package_name',
executable='node_executable',
name='node_name',
parameters=[{'param': value}]
),
])
Launch File Structure for pykal
Since pykal nodes are created programmatically (not from executables), we use a different approach:
Option 1: Create a Standalone Script
Create a Python script that runs nodes:
# scripts/turtlebot_system.py
import rclpy
from pykal_demos.turtlebot_nodes import (
create_waypoint_node,
create_controller_node,
create_simulator_node,
create_kf_node
)
def main():
rclpy.init()
# Create nodes
waypoint_node = create_waypoint_node()
controller_node = create_controller_node()
simulator_node = create_simulator_node()
kf_node = create_kf_node()
# Create and start
waypoint_node.create_node()
controller_node.create_node()
simulator_node.create_node()
kf_node.create_node()
waypoint_node.start()
controller_node.start()
simulator_node.start()
kf_node.start()
# Keep running
try:
rclpy.spin(waypoint_node._node)
except KeyboardInterrupt:
pass
# Cleanup
waypoint_node.stop()
controller_node.stop()
simulator_node.stop()
kf_node.stop()
rclpy.shutdown()
if __name__ == '__main__':
main()
Then launch with:
python3 scripts/turtlebot_system.py
Option 2: Use Launch File to Run Script
Create a launch file that runs the script:
# launch/turtlebot_navigation.launch.py
from launch import LaunchDescription
from launch.actions import ExecuteProcess
import os
def generate_launch_description():
script_path = os.path.join(
os.path.dirname(__file__),
'..',
'scripts',
'turtlebot_system.py'
)
return LaunchDescription([
ExecuteProcess(
cmd=['python3', script_path],
output='screen'
)
])
Then launch with:
ros2 launch pykal_demos turtlebot_navigation.launch.py
Option 3: Multi-Process Launch (Recommended)
Launch each ROSNode as a separate process for better isolation:
# launch/turtlebot_multiprocess.launch.py
from launch import LaunchDescription
from launch.actions import ExecuteProcess
import os
def generate_launch_description():
scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts')
return LaunchDescription([
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'waypoint_node.py')],
output='screen',
prefix='xterm -e' # Open in separate terminal
),
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'controller_node.py')],
output='screen',
prefix='xterm -e'
),
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'simulator_node.py')],
output='screen',
prefix='xterm -e'
),
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'kf_node.py')],
output='screen',
prefix='xterm -e'
),
])
Each node runs in its own terminal window for easy debugging!
Example: TurtleBot Launch System
Let’s create a complete launch system for TurtleBot navigation.
Directory Structure:
pykal_demos/
├── launch/
│ ├── turtlebot_software.launch.py
│ ├── turtlebot_gazebo.launch.py
│ └── crazyflie_fusion.launch.py
├── scripts/
│ ├── turtlebot_waypoint_node.py
│ ├── turtlebot_controller_node.py
│ ├── turtlebot_simulator_node.py
│ └── turtlebot_kf_node.py
└── config/
└── turtlebot_params.yaml
1. Create Node Scripts
Each node in its own file:
# scripts/turtlebot_waypoint_node.py
import rclpy
from pykal.ros.ros_node import ROSNode
from geometry_msgs.msg import PoseStamped
import numpy as np
def create_waypoint_node():
# ... (node creation code)
pass
def main():
rclpy.init()
node = create_waypoint_node()
node.create_node()
node.start()
try:
rclpy.spin(node._node)
except KeyboardInterrupt:
pass
node.stop()
rclpy.shutdown()
if __name__ == '__main__':
main()
2. Create Launch File
# launch/turtlebot_software.launch.py
from launch import LaunchDescription
from launch.actions import ExecuteProcess, DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
import os
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# Declare arguments
use_sim_time = LaunchConfiguration('use_sim_time', default='false')
# Get package directory
pkg_dir = get_package_share_directory('pykal_demos')
scripts_dir = os.path.join(pkg_dir, 'scripts')
return LaunchDescription([
# Arguments
DeclareLaunchArgument(
'use_sim_time',
default_value='false',
description='Use simulation time'
),
# Nodes
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'turtlebot_waypoint_node.py')],
output='screen'
),
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'turtlebot_controller_node.py')],
output='screen'
),
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'turtlebot_simulator_node.py')],
output='screen'
),
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'turtlebot_kf_node.py')],
output='screen'
),
])
3. Launch
ros2 launch pykal_demos turtlebot_software.launch.py
Launching with Gazebo
For Gazebo integration, modify the launch file:
# launch/turtlebot_gazebo.launch.py
from launch import LaunchDescription
from launch.actions import ExecuteProcess, IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
import os
def generate_launch_description():
# Start Gazebo
gazebo_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
os.path.join(
get_package_share_directory('gazebo_ros'),
'launch',
'gazebo.launch.py'
)
])
)
# Spawn TurtleBot model
spawn_robot = ExecuteProcess(
cmd=['ros2', 'run', 'gazebo_ros', 'spawn_entity.py',
'-entity', 'turtlebot3',
'-topic', 'robot_description'],
output='screen'
)
# Start pykal nodes (NO simulator this time!)
pkg_dir = get_package_share_directory('pykal_demos')
scripts_dir = os.path.join(pkg_dir, 'scripts')
return LaunchDescription([
gazebo_launch,
spawn_robot,
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'turtlebot_waypoint_node.py')],
output='screen'
),
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'turtlebot_controller_node.py')],
output='screen'
),
# NOTE: NO simulator_node - Gazebo provides /odom!
ExecuteProcess(
cmd=['python3', os.path.join(scripts_dir, 'turtlebot_kf_node.py')],
output='screen'
),
])
Parameter Files
Use YAML files for configuration:
# config/turtlebot_params.yaml
waypoint_generator:
ros__parameters:
rate_hz: 10.0
waypoints:
- [2.0, 0.0, 0.0]
- [2.0, 2.0, 1.57]
- [0.0, 2.0, 3.14]
- [0.0, 0.0, -1.57]
switch_time: 15.0
velocity_controller:
ros__parameters:
Kp: 0.5
Komega: 1.5
rate_hz: 50.0
kalman_filter:
ros__parameters:
dt: 0.1
rate_hz: 10.0
Q_diag: [0.01, 0.01, 0.02, 0.1, 0.1]
R_diag: [0.05, 0.05, 0.1]
Load in launch file:
from launch_ros.actions import Node
Node(
package='pykal_demos',
executable='turtlebot_kf_node.py',
parameters=[os.path.join(pkg_dir, 'config', 'turtlebot_params.yaml')]
)
Advanced Launch Features
Conditional Launching
Launch different nodes based on arguments:
from launch.conditions import IfCondition, UnlessCondition
use_gazebo = LaunchConfiguration('gazebo')
# Launch simulator only if NOT using Gazebo
ExecuteProcess(
cmd=['python3', 'simulator_node.py'],
condition=UnlessCondition(use_gazebo)
)
# Launch Gazebo only if using Gazebo
IncludeLaunchDescription(...,
condition=IfCondition(use_gazebo)
)
Node Dependencies
Ensure nodes start in correct order:
from launch.actions import RegisterEventHandler
from launch.event_handlers import OnProcessStart
# Start controller only after simulator is ready
RegisterEventHandler(
OnProcessStart(
target_action=simulator_node,
on_start=[controller_node]
)
)
Namespace Management
Run multiple robots with namespaces:
robot1 = Node(
package='pykal_demos',
executable='turtlebot_system',
namespace='robot1'
)
robot2 = Node(
package='pykal_demos',
executable='turtlebot_system',
namespace='robot2'
)
Topics become: /robot1/odom, /robot2/odom, etc.
Testing Launch Files
Verify your launch file works:
# Check syntax
ros2 launch --show-args pykal_demos turtlebot_software.launch.py
# List all nodes that will be launched
ros2 launch --show-nodes pykal_demos turtlebot_software.launch.py
# Actually launch
ros2 launch pykal_demos turtlebot_software.launch.py
Debugging launch issues:
# Verbose output
ros2 launch pykal_demos turtlebot_software.launch.py --debug
# Check if nodes are running
ros2 node list
# Check topics
ros2 topic list
# Monitor logs
ros2 launch pykal_demos turtlebot_software.launch.py --screen
Best Practices
One launch file per use case: -
turtlebot_software.launch.py- Software simulation -turtlebot_gazebo.launch.py- Gazebo integration -turtlebot_hardware.launch.py- Real hardwareParameterize everything: - Use launch arguments for flexibility - Store parameters in YAML files - Allow runtime configuration
Descriptive naming: - Launch files:
<robot>_<use_case>.launch.py- Nodes:<robot>_<component>_node.py- Configs:<robot>_params.yamlDocument your launch files: - Add docstrings - Explain arguments - Provide usage examples
Test incrementally: - Start with one node - Add nodes one at a time - Verify with
rqt_graph
Summary
Launch files are essential for professional ROS development:
✓ Automation: Start entire systems with one command ✓ Configuration: Centralize parameters ✓ Reproducibility: Share exact setups ✓ Flexibility: Switch between simulation/hardware ✓ Debugging: Better logging and process management
For pykal systems:
Create standalone node scripts
Create launch file to run scripts
Use parameter files for configuration
Test with
ros2 launchVerify with
rqt_graph
Next: Apply this to your TurtleBot and Crazyflie systems for professional deployment!