Skip to main content

AI Assistant

Physical AI & Humanoid Robotics

Hello! I'm your AI assistant for the AI-Native Guide to Physical AI & Humanoid Robotics. How can I help you today?

04:57 AM

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

  1. Always include inertial properties: Physics simulation requires mass and inertia
  2. Collision geometry can be simpler: Use boxes/cylinders for faster collision checks
  3. Visual geometry can be detailed: Use meshes for appearance
  4. Validate URDF: Use check_urdf command before using in simulation
  5. 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

  1. Use Python launch files: More flexible than XML
  2. Organize by subsystem: Separate launch files for perception, control, planning
  3. Use parameters: Don't hardcode values
  4. Document arguments: Add descriptions for launch arguments
  5. 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 info to check subscriptions
  • Monitor with htop or top
  • 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