Topic 3 — URDF, RViz, Launch Files & Debugging
This topic covers the practical tools you'll use daily in ROS 2 development: URDF for describing robot morphology, RViz for visualization, Launch Files for orchestrating systems, and Debugging Tools for troubleshooting.
3.1 URDF: Describing Robot Morphology
What is URDF?
URDF (Unified Robot Description Format) is an XML format for describing robot structure:
- Links: Rigid bodies (e.g., torso, upper arm, hand)
- Joints: Connections between links (revolute, prismatic, fixed)
- Visual: Appearance (meshes, colors)
- Collision: Physics collision geometry
- Inertial: Mass, center of mass, inertia tensor
Why URDF matters:
- Kinematics: Forward/inverse kinematics calculations
- Simulation: Physics engines (Gazebo, Isaac Sim) use URDF
- Visualization: RViz displays URDF models
- Planning: Motion planners need robot structure
URDF Structure
A minimal URDF consists of:
<?xml version="1.0"?>
<robot name="simple_robot">
<!-- Links -->
<link name="base_link">
<visual>
<geometry>
<box size="0.5 0.5 0.1"/>
</geometry>
<material name="blue">
<color rgba="0 0 1 1"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.5 0.5 0.1"/>
</geometry>
</collision>
<inertial>
<mass value="10.0"/>
<inertia ixx="0.1" ixy="0" ixz="0" iyy="0.1" iyz="0" izz="0.1"/>
</inertial>
</link>
<!-- Joints -->
<joint name="joint1" type="revolute">
<parent link="base_link"/>
<child link="link1"/>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
<limit lower="-3.14" upper="3.14" effort="100" velocity="2.0"/>
</joint>
<link name="link1">
<!-- Link definition -->
</link>
</robot>
Joint Types
Revolute: Rotational joint (most common for arms)
<joint name="shoulder_joint" type="revolute">
<limit lower="-1.57" upper="1.57" effort="50" velocity="3.0"/>
</joint>
Prismatic: Linear joint (for sliding mechanisms)
<joint name="slider_joint" type="prismatic">
<limit lower="0.0" upper="1.0" effort="100" velocity="0.5"/>
</joint>
Fixed: Rigid connection (no motion)
<joint name="fixed_joint" type="fixed">
<parent link="base"/>
<child link="camera_mount"/>
</joint>
Floating: 6-DOF free motion (for base)
<joint name="base_joint" type="floating">
<parent link="world"/>
<child link="base_link"/>
</joint>
Humanoid URDF Example
Here's a simplified humanoid upper body:
<?xml version="1.0"?>
<robot name="humanoid_upper_body">
<!-- Base/Torso -->
<link name="torso">
<visual>
<geometry>
<box size="0.3 0.2 0.4"/>
</geometry>
<material name="torso_material">
<color rgba="0.8 0.8 0.8 1"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.3 0.2 0.4"/>
</geometry>
</collision>
<inertial>
<mass value="15.0"/>
<inertia ixx="0.1" ixy="0" ixz="0" iyy="0.1" iyz="0" izz="0.1"/>
</inertial>
</link>
<!-- Left Shoulder -->
<joint name="left_shoulder_pitch" type="revolute">
<parent link="torso"/>
<child link="left_upper_arm"/>
<origin xyz="0.15 0.1 0.3" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit lower="-1.57" upper="1.57" effort="50" velocity="3.0"/>
</joint>
<link name="left_upper_arm">
<visual>
<geometry>
<cylinder radius="0.05" length="0.3"/>
</geometry>
<material name="arm_material">
<color rgba="0.9 0.9 0.9 1"/>
</material>
</visual>
<collision>
<geometry>
<cylinder radius="0.05" length="0.3"/>
</geometry>
</collision>
<inertial>
<mass value="2.0"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
</link>
<!-- Left Elbow -->
<joint name="left_elbow" type="revolute">
<parent link="left_upper_arm"/>
<child link="left_lower_arm"/>
<origin xyz="0 0 0.15" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit lower="0" upper="2.0" effort="30" velocity="4.0"/>
</joint>
<link name="left_lower_arm">
<visual>
<geometry>
<cylinder radius="0.04" length="0.25"/>
</geometry>
<material name="arm_material"/>
</visual>
<collision>
<geometry>
<cylinder radius="0.04" length="0.25"/>
</geometry>
</collision>
<inertial>
<mass value="1.5"/>
<inertia ixx="0.008" ixy="0" ixz="0" iyy="0.008" iyz="0" izz="0.008"/>
</inertial>
</link>
<!-- Left Hand (simplified) -->
<joint name="left_wrist" type="fixed">
<parent link="left_lower_arm"/>
<child link="left_hand"/>
<origin xyz="0 0 0.125" rpy="0 0 0"/>
</joint>
<link name="left_hand">
<visual>
<geometry>
<box size="0.08 0.08 0.05"/>
</geometry>
<material name="hand_material">
<color rgba="0.7 0.7 0.7 1"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.08 0.08 0.05"/>
</geometry>
</collision>
<inertial>
<mass value="0.5"/>
<inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/>
</inertial>
</link>
<!-- Repeat for right arm... -->
</robot>
URDF Best Practices
- Always include inertial properties: Physics simulation requires mass and inertia
- Collision geometry can be simpler: Use boxes/cylinders for faster collision checks
- Visual geometry can be detailed: Use meshes for appearance
- Validate URDF: Use
check_urdfcommand before using in simulation - Use xacro for complex robots: Xacro allows parameterization and includes
Validating URDF
# Check URDF syntax
check_urdf humanoid.urdf
# Visualize URDF structure tree
urdf_to_graphiz humanoid.urdf
3.2 RViz: Visualizing Robots and Sensor Data
What is RViz?
RViz (ROS Visualization) is a 3D visualization tool for ROS 2:
- Display URDF models: View robot structure
- Visualize sensor data: Point clouds, images, laser scans
- Show TF frames: Coordinate transformations
- Display markers: Trajectories, goals, obstacles
- Interactive controls: Move robot joints, set goals
Loading URDF in RViz
Method 1: Using robot_state_publisher
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from robot_state_publisher import RobotStatePublisher
import urdf_parser_py.urdf
class URDFPublisher(Node):
def __init__(self):
super().__init__('urdf_publisher')
# Load URDF
urdf_file = '/path/to/humanoid.urdf'
with open(urdf_file, 'r') as f:
urdf_string = f.read()
robot = urdf_parser_py.urdf.URDF.from_xml_string(urdf_string)
# Publish robot state
self.robot_pub = RobotStatePublisher(robot, 'base_link')
# Publish joint states
from sensor_msgs.msg import JointState
self.joint_pub = self.create_publisher(JointState, 'joint_states', 10)
self.timer = self.create_timer(0.1, self.publish_joints)
def publish_joints(self):
msg = JointState()
msg.name = ['left_shoulder_pitch', 'left_elbow']
msg.position = [0.5, 1.0] # Joint angles
msg.velocity = [0.0, 0.0]
self.joint_pub.publish(msg)
def main(args=None):
rclpy.init(args=args)
node = URDFPublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Method 2: Using launch file
<launch>
<param name="robot_description" textfile="$(find your_package)/urdf/humanoid.urdf"/>
<node name="robot_state_publisher" pkg="robot_state_publisher" exec="robot_state_publisher">
<param name="robot_description" value="$(var robot_description)"/>
</node>
<node name="rviz" pkg="rviz2" exec="rviz2" args="-d $(find your_package)/config/humanoid.rviz"/>
</launch>
Visualizing Sensor Data
Point Cloud (LiDAR):
from sensor_msgs.msg import PointCloud2
import sensor_msgs_py.point_cloud2 as pc2
# In RViz, add "PointCloud2" display
# Set topic to /lidar/points
Camera Image:
from sensor_msgs.msg import Image
# In RViz, add "Image" display
# Set topic to /camera/rgb/image_raw
Laser Scan:
from sensor_msgs.msg import LaserScan
# In RViz, add "LaserScan" display
# Set topic to /scan
Publishing Markers
Visualize trajectories, goals, obstacles:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from visualization_msgs.msg import Marker
from geometry_msgs.msg import Point
class TrajectoryVisualizer(Node):
def __init__(self):
super().__init__('trajectory_visualizer')
self.marker_pub = self.create_publisher(Marker, 'trajectory_marker', 10)
self.timer = self.create_timer(1.0, self.publish_marker)
def publish_marker(self):
marker = Marker()
marker.header.frame_id = "base_link"
marker.header.stamp = self.get_clock().now().to_msg()
marker.ns = "trajectory"
marker.id = 0
marker.type = Marker.LINE_STRIP
marker.action = Marker.ADD
# Trajectory points
marker.points = [
Point(x=0.0, y=0.0, z=0.0),
Point(x=1.0, y=0.5, z=0.0),
Point(x=2.0, y=1.0, z=0.0),
Point(x=3.0, y=1.5, z=0.0)
]
marker.scale.x = 0.05 # Line width
marker.color.a = 1.0
marker.color.r = 1.0
marker.color.g = 0.0
marker.color.b = 0.0
self.marker_pub.publish(marker)
def main(args=None):
rclpy.init(args=args)
node = TrajectoryVisualizer()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
RViz Configuration Files
Save RViz configuration (.rviz file) for consistent visualization:
# humanoid.rviz
Panels:
- Class: rviz_common/Displays
Name: Displays
- Class: rviz_common/Selection
Name: Selection
- Class: rviz_common/Tool Properties
Name: Tool Properties
- Class: rviz_common/Views
Name: Views
Visualization Manager:
Class: ""
Displays:
- Alpha: 0.5
Class: rviz_default_plugins/RobotModel
Description: The robot model
Name: RobotModel
Value: true
Visual Enabled: true
3.3 Launch Files: Orchestrating Multi-Node Systems
Why Launch Files?
Starting 10+ nodes manually is error-prone:
# Manual startup (tedious!)
ros2 run package1 node1 &
ros2 run package2 node2 --param value &
ros2 run package3 node3 --remap topic1:=topic2 &
# ... 7 more nodes
Launch files automate this:
# Single command starts everything
ros2 launch your_package humanoid.launch.py
Launch File Syntax (Python)
ROS 2 uses Python launch files (.launch.py):
#!/usr/bin/env python3
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
# Launch arguments
use_sim_time = LaunchConfiguration('use_sim_time', default='false')
return LaunchDescription([
DeclareLaunchArgument(
'use_sim_time',
default_value='false',
description='Use simulation time'
),
# Node 1: Sensor simulator
Node(
package='your_package',
executable='sensor_simulator',
name='sensor_simulator',
parameters=[{'use_sim_time': use_sim_time}],
remappings=[('sensor_data', '/sensors/data')]
),
# Node 2: Planner service
Node(
package='your_package',
executable='planner_service',
name='planner',
parameters=[{
'planning_timeout': 5.0,
'max_velocity': 1.0
}]
),
# Node 3: Motor controller
Node(
package='your_package',
executable='motor_controller',
name='motor_controller',
remappings=[
('motor_commands', '/control/motor_commands'),
('joint_states', '/sensors/joint_states')
]
),
# Node 4: RViz
Node(
package='rviz2',
executable='rviz2',
name='rviz',
arguments=['-d', '/path/to/config.rviz']
)
])
Launch File Syntax (XML)
ROS 2 also supports XML launch files:
<launch>
<arg name="use_sim_time" default="false"/>
<node pkg="your_package" exec="sensor_simulator" name="sensor_simulator">
<param name="use_sim_time" value="$(var use_sim_time)"/>
<remap from="sensor_data" to="/sensors/data"/>
</node>
<node pkg="your_package" exec="planner_service" name="planner">
<param name="planning_timeout" value="5.0"/>
<param name="max_velocity" value="1.0"/>
</node>
<node pkg="your_package" exec="motor_controller" name="motor_controller">
<remap from="motor_commands" to="/control/motor_commands"/>
<remap from="joint_states" to="/sensors/joint_states"/>
</node>
</launch>
Conditional Logic
Python launch files support conditionals:
from launch.conditions import IfCondition, UnlessCondition
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
use_gui = LaunchConfiguration('use_gui', default='true')
return LaunchDescription([
# Always start this node
Node(
package='your_package',
executable='core_node',
name='core'
),
# Only start GUI if requested
Node(
package='rviz2',
executable='rviz2',
name='rviz',
condition=IfCondition(use_gui)
),
# Start simulator only if not using real hardware
Node(
package='gazebo_ros',
executable='gazebo',
name='gazebo',
condition=UnlessCondition('use_real_hardware')
)
])
Including Other Launch Files
Modular design with includes:
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
return LaunchDescription([
# Include perception launch file
IncludeLaunchDescription(
PythonLaunchDescriptionSource([
get_package_share_directory('perception_package'),
'/launch/perception.launch.py'
])
),
# Include control launch file
IncludeLaunchDescription(
PythonLaunchDescriptionSource([
get_package_share_directory('control_package'),
'/launch/control.launch.py'
])
)
])
Best Practices
- Use Python launch files: More flexible than XML
- Organize by subsystem: Separate launch files for perception, control, planning
- Use parameters: Don't hardcode values
- Document arguments: Add descriptions for launch arguments
- Handle errors: Check if nodes exist before launching
3.4 Debugging ROS 2 Systems
Command-Line Tools
Node inspection:
# List all nodes
ros2 node list
# Get node info
ros2 node info /node_name
# List node's topics/services/actions
ros2 node info /planner
Topic inspection:
# List all topics
ros2 topic list
# Echo topic messages
ros2 topic echo /sensor_data
# Check topic frequency
ros2 topic hz /sensor_data
# Check topic bandwidth
ros2 topic bw /sensor_data
# Get topic info (type, publishers, subscribers)
ros2 topic info /sensor_data
# Publish test message
ros2 topic pub /sensor_data std_msgs/String "data: 'test'"
Service inspection:
# List all services
ros2 service list
# Get service info
ros2 service type /plan_trajectory
# Call service
ros2 service call /plan_trajectory your_package/srv/PlanTrajectory "{start: {x: 0.0, y: 0.0}, goal: {x: 5.0, y: 3.0}}"
Action inspection:
# List all actions
ros2 action list
# Get action info
ros2 action info /navigate_to_goal
# Send action goal
ros2 action send_goal /navigate_to_goal your_package/action/Navigate "{target: {x: 5.0, y: 3.0}}"
Parameter inspection:
# List all parameters
ros2 param list
# Get parameter value
ros2 param get /node_name parameter_name
# Set parameter
ros2 param set /node_name parameter_name value
# Dump all parameters
ros2 param dump /node_name
Visualization Tools
rqt_graph: Visualize computation graph
rqt_graph
Shows nodes, topics, services, and their connections.
rqt_topic: Inspect topic messages
rqt_topic
Browse topics, view message contents, monitor frequency.
rqt_service: Call services interactively
rqt_service
Browse services, call with GUI, view responses.
Bag Recording and Playback
Record sensor data:
# Record all topics
ros2 bag record -a
# Record specific topics
ros2 bag record /sensor_data /camera/rgb /lidar/scan
# Record with compression
ros2 bag record -a --compression-mode file --compression-format zstd
Playback recorded data:
# Play bag file
ros2 bag play my_bag/
# Play at different rate
ros2 bag play my_bag/ --rate 0.5 # Half speed
# Play with loop
ros2 bag play my_bag/ --loop
Logging
Python logging in nodes:
import rclpy
from rclpy.node import Node
class MyNode(Node):
def __init__(self):
super().__init__('my_node')
# Logging levels
self.get_logger().debug('Debug message')
self.get_logger().info('Info message')
self.get_logger().warn('Warning message')
self.get_logger().error('Error message')
self.get_logger().fatal('Fatal message')
Set logging level:
# Set node logging level
ros2 run your_package node_name --ros-args --log-level debug
# Set global logging level
export RCUTILS_LOGGING_SEVERITY=DEBUG
Common Issues and Solutions
Issue: Node won't start
- Check dependencies:
rosdep install --from-paths src --ignore-src -r -y - Check YAML syntax in launch files
- Check node executable exists:
which ros2
Issue: No data on topic
- Check publisher is running:
ros2 node list - Check QoS compatibility:
ros2 topic info /topic - Check topic name matches:
ros2 topic list
Issue: Service calls timeout
- Check server is running:
ros2 service list - Check service type matches:
ros2 service type /service - Increase timeout in client code
Issue: Memory leaks
- Use
ros2 node infoto check subscriptions - Monitor with
htoportop - Check for unbounded queues in QoS
Summary
URDF: Describes robot structure (links, joints, inertial properties)
RViz: Visualizes robots, sensor data, TF frames, markers
Launch Files: Orchestrate multi-node systems with parameters and remapping
Debugging Tools: CLI (ros2), visualization (rqt), recording (ros2 bag)
These tools are essential for building, testing, and debugging ROS 2 systems. Master them early, and you'll save hours of debugging time.
References
- URDF Tutorial: http://wiki.ros.org/urdf/Tutorials
- RViz User Guide: https://docs.ros.org/en/humble/Tutorials/Intermediate/RViz/RViz-User-Guide/RViz-User-Guide.html
- Launch Files: https://docs.ros.org/en/humble/Tutorials/Intermediate/Launch/Launch-Main.html
- ROS 2 CLI Tools: https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Introducing-Turtlesim/Introducing-Turtlesim.html